mirror of
https://github.com/cockpit-project/cockpit.git
synced 2026-11-03 21:49:50 -09:00
src/bridge: drop the C bridge
Nothing is using this code anymore. All that's left in this directory are a few test resources: we'll deal with those in the next commit. Clean up a few stray references in the build system and packaging: in paticular, remove --enable flags for both PCP and PolKit.
This commit is contained in:
parent
8463df0251
commit
7c7ab45e3f
107 changed files with 1 additions and 38074 deletions
|
|
@ -169,7 +169,6 @@ include src/branding/opensuse/Makefile.am
|
|||
include src/branding/rhel/Makefile.am
|
||||
include src/branding/scientific/Makefile.am
|
||||
include src/branding/ubuntu/Makefile.am
|
||||
include src/bridge/Makefile.am
|
||||
include src/client/Makefile.am
|
||||
include src/common/Makefile-common.am
|
||||
include src/pam-ssh-add/Makefile.am
|
||||
|
|
|
|||
53
configure.ac
53
configure.ac
|
|
@ -61,18 +61,6 @@ AM_CONDITIONAL(SELINUX_POLICY_ENABLED, test "$enable_selinux_policy" != "no")
|
|||
AC_SUBST(SELINUX_POLICY_TYPE, [${enable_selinux_policy}])
|
||||
AC_MSG_RESULT($enable_selinux_policy)
|
||||
|
||||
# --disable-polkit
|
||||
AC_MSG_CHECKING([whether to build polkit support])
|
||||
AC_ARG_ENABLE(polkit, AS_HELP_STRING([--disable-polkit], [Disable usage of polkit]))
|
||||
AM_CONDITIONAL(WITH_POLKIT, test "$enable_polkit" != 'no')
|
||||
if test "$enable_polkit" != 'no'; then
|
||||
AC_DEFINE([WITH_POLKIT], 1, [Whether to build with polkit support])
|
||||
fi
|
||||
AC_MSG_RESULT(${enable_polkit:=yes})
|
||||
|
||||
# TODO: remove this and clean up the old bridge in src/bridge/
|
||||
AM_CONDITIONAL(WITH_OLD_BRIDGE, false)
|
||||
|
||||
AC_SEARCH_LIBS([argp_parse], [argp])
|
||||
case "$ac_cv_search_argp_parse" in
|
||||
no) AC_MSG_FAILURE([failed to find argp_parse]) ;;
|
||||
|
|
@ -99,9 +87,6 @@ PKG_CHECK_MODULES(libsystemd, [libsystemd >= 235])
|
|||
PKG_CHECK_MODULES(json_glib, [json-glib-1.0 >= 1.4])
|
||||
PKG_CHECK_MODULES(gnutls, [gnutls >= 3.6.0])
|
||||
PKG_CHECK_MODULES(krb5, [krb5-gssapi >= 1.11 krb5 >= 1.11])
|
||||
if test "$enable_polkit" != "no"; then
|
||||
PKG_CHECK_MODULES(polkit, [polkit-agent-1 >= 0.105])
|
||||
fi
|
||||
|
||||
# pam
|
||||
AC_CHECK_HEADER([security/pam_appl.h], ,
|
||||
|
|
@ -127,42 +112,6 @@ AC_CHECK_LIB(crypt, crypt_r, [ true ],
|
|||
)
|
||||
COCKPIT_WS_LIBS="$COCKPIT_WS_LIBS -lcrypt"
|
||||
|
||||
# pcp
|
||||
AC_MSG_CHECKING([whether to build with PCP])
|
||||
AC_ARG_ENABLE(pcp, AS_HELP_STRING([--disable-pcp], [Disable usage of PCP]))
|
||||
|
||||
if test "$enable_pcp" = "no"; then
|
||||
AC_MSG_RESULT($enable_pcp)
|
||||
|
||||
else
|
||||
if test "$enable_pcp" = ""; then
|
||||
disable_msg="(perhaps --disable-pcp)"
|
||||
fi
|
||||
|
||||
enable_pcp="yes"
|
||||
AC_MSG_RESULT($enable_pcp)
|
||||
|
||||
AC_CHECK_HEADER([pcp/pmapi.h], ,
|
||||
[AC_MSG_ERROR([Couldn't find pcp headers $disable_msg])]
|
||||
)
|
||||
AC_CHECK_HEADERS([pcp/import.h pcp/pmda.h], ,
|
||||
[AC_MSG_ERROR([Couldn't find pcp headers $disable_msg])],
|
||||
[#include <pcp/pmapi.h>]
|
||||
)
|
||||
AC_CHECK_LIB(pcp, pmNewContext, [ true ],
|
||||
[AC_MSG_ERROR([Couldn't find pcp library $disable_msg])]
|
||||
)
|
||||
AC_CHECK_LIB(pcp_pmda, pmdaCacheLookup, [ true ],
|
||||
[AC_MSG_ERROR([Couldn't find pcp_pmda library $disable_msg])]
|
||||
)
|
||||
AC_CHECK_LIB(pcp_import, pmiStart, [ true ],
|
||||
[AC_MSG_ERROR([Couldn't find pcp_import library $disable_msg])]
|
||||
)
|
||||
COCKPIT_PCP_LIBS="$COCKPIT_PCP_LIBS -lpcp"
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL([ENABLE_PCP], [test "$enable_pcp" = "yes"])
|
||||
|
||||
# systemd
|
||||
AC_ARG_WITH([systemdunitdir], [AS_HELP_STRING([--with-systemdunitdir=DIR],
|
||||
[directory to install systemd unit files in])])
|
||||
|
|
@ -423,8 +372,6 @@ echo "
|
|||
Node environment: ${NODE_ENV}
|
||||
With coverage: ${enable_coverage}
|
||||
With address sanitizer: ${asan_status}
|
||||
With PCP: ${enable_pcp}
|
||||
With polkit: ${enable_polkit}
|
||||
SELinux Policy: ${enable_selinux_policy}
|
||||
|
||||
cockpit-client: ${enable_cockpit_client}
|
||||
|
|
|
|||
|
|
@ -103,8 +103,6 @@ def create_manifest(
|
|||
'buildsystem': 'autotools',
|
||||
'config-opts': [
|
||||
'--enable-cockpit-client',
|
||||
'--disable-polkit',
|
||||
'--disable-pcp',
|
||||
'--with-systemdunitdir=/invalid',
|
||||
'CPPFLAGS=-Itools/mock-build-env',
|
||||
'--with-admin-group=root',
|
||||
|
|
|
|||
|
|
@ -1,297 +0,0 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# libcockpit-metrics.a: code used in both cockpit-bridge and cockpit-pcp
|
||||
|
||||
noinst_LIBRARIES += libcockpit-metrics.a
|
||||
|
||||
libcockpit_metrics_a_CPPFLAGS = \
|
||||
-DG_LOG_DOMAIN=\"cockpit-bridge\" \
|
||||
$(glib_CFLAGS) \
|
||||
$(json_glib_CFLAGS) \
|
||||
$(AM_CPPFLAGS)
|
||||
|
||||
libcockpit_metrics_a_LIBS = \
|
||||
libcockpit-metrics.a \
|
||||
$(libcockpit_common_a_LIBS) \
|
||||
$(fts_LIBS) \
|
||||
-lm \
|
||||
$(NULL)
|
||||
|
||||
libcockpit_metrics_a_SOURCES = \
|
||||
src/bridge/cockpitblocksamples.c \
|
||||
src/bridge/cockpitblocksamples.h \
|
||||
src/bridge/cockpitcgroupsamples.c \
|
||||
src/bridge/cockpitcgroupsamples.h \
|
||||
src/bridge/cockpitcpusamples.c \
|
||||
src/bridge/cockpitcpusamples.h \
|
||||
src/bridge/cockpitdisksamples.c \
|
||||
src/bridge/cockpitdisksamples.h \
|
||||
src/bridge/cockpitinternalmetrics.c \
|
||||
src/bridge/cockpitinternalmetrics.h \
|
||||
src/bridge/cockpitmemorysamples.c \
|
||||
src/bridge/cockpitmemorysamples.h \
|
||||
src/bridge/cockpitmetrics.c \
|
||||
src/bridge/cockpitmetrics.h \
|
||||
src/bridge/cockpitmountsamples.c \
|
||||
src/bridge/cockpitmountsamples.h \
|
||||
src/bridge/cockpitnetworksamples.c \
|
||||
src/bridge/cockpitnetworksamples.h \
|
||||
src/bridge/cockpitsamples.c \
|
||||
src/bridge/cockpitsamples.h \
|
||||
$(NULL)
|
||||
|
||||
# TODO: remove all of the below
|
||||
if WITH_OLD_BRIDGE
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# libcockpit-bridge.a: code used in cockpit-bridge and its tests
|
||||
|
||||
noinst_LIBRARIES += libcockpit-bridge.a
|
||||
|
||||
libcockpit_bridge_a_CPPFLAGS = \
|
||||
-DG_LOG_DOMAIN=\"cockpit-bridge\" \
|
||||
$(glib_CFLAGS) \
|
||||
$(json_glib_CFLAGS) \
|
||||
$(AM_CPPFLAGS)
|
||||
|
||||
libcockpit_bridge_a_LIBS = \
|
||||
libcockpit-bridge.a \
|
||||
$(libcockpit_metrics_a_LIBS) \
|
||||
$(libsystemd_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
libcockpit_bridge_a_SOURCES = \
|
||||
src/bridge/cockpitconnect.c \
|
||||
src/bridge/cockpitconnect.h \
|
||||
src/bridge/cockpitdbuscache.c \
|
||||
src/bridge/cockpitdbuscache.h \
|
||||
src/bridge/cockpitdbusconfig.c \
|
||||
src/bridge/cockpitdbusinternal.c \
|
||||
src/bridge/cockpitdbusinternal.h \
|
||||
src/bridge/cockpitdbusjson.c \
|
||||
src/bridge/cockpitdbusjson.h \
|
||||
src/bridge/cockpitdbusloginmessages.c \
|
||||
src/bridge/cockpitdbusmachines.c \
|
||||
src/bridge/cockpitdbusmeta.c \
|
||||
src/bridge/cockpitdbusmeta.h \
|
||||
src/bridge/cockpitdbusprocess.c \
|
||||
src/bridge/cockpitdbusrules.c \
|
||||
src/bridge/cockpitdbusrules.h \
|
||||
src/bridge/cockpitdbususer.c \
|
||||
src/bridge/cockpitechochannel.c \
|
||||
src/bridge/cockpitechochannel.h \
|
||||
src/bridge/cockpitfslist.c \
|
||||
src/bridge/cockpitfslist.h \
|
||||
src/bridge/cockpitfsread.c \
|
||||
src/bridge/cockpitfsread.h \
|
||||
src/bridge/cockpitfsreplace.c \
|
||||
src/bridge/cockpitfsreplace.h \
|
||||
src/bridge/cockpitfswatch.c \
|
||||
src/bridge/cockpitfswatch.h \
|
||||
src/bridge/cockpithttpstream.c \
|
||||
src/bridge/cockpithttpstream.h \
|
||||
src/bridge/cockpitinteracttransport.c \
|
||||
src/bridge/cockpitinteracttransport.h \
|
||||
src/bridge/cockpitnullchannel.c \
|
||||
src/bridge/cockpitnullchannel.h \
|
||||
src/bridge/cockpitpackages.c \
|
||||
src/bridge/cockpitpackages.h \
|
||||
src/bridge/cockpitpacketchannel.c \
|
||||
src/bridge/cockpitpacketchannel.h \
|
||||
src/bridge/cockpitpaths.c \
|
||||
src/bridge/cockpitpaths.h \
|
||||
src/bridge/cockpitpeer.c \
|
||||
src/bridge/cockpitpeer.h \
|
||||
src/bridge/cockpitpipechannel.c \
|
||||
src/bridge/cockpitpipechannel.h \
|
||||
src/bridge/cockpitrouter.c \
|
||||
src/bridge/cockpitrouter.h \
|
||||
src/bridge/cockpitstream.c \
|
||||
src/bridge/cockpitstream.h \
|
||||
src/bridge/cockpitwebsocketstream.c \
|
||||
src/bridge/cockpitwebsocketstream.h \
|
||||
$(NULL)
|
||||
|
||||
if WITH_POLKIT
|
||||
libcockpit_bridge_a_CPPFLAGS += $(polkit_CFLAGS)
|
||||
libcockpit_bridge_a_LIBS += $(polkit_LIBS)
|
||||
libcockpit_bridge_a_SOURCES += \
|
||||
src/bridge/cockpitpolkitagent.c \
|
||||
src/bridge/cockpitpolkitagent.h \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# PROGRAMS
|
||||
|
||||
bin_PROGRAMS += cockpit-bridge
|
||||
cockpit_bridge_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS)
|
||||
cockpit_bridge_LDADD = $(libcockpit_bridge_a_LIBS)
|
||||
cockpit_bridge_SOURCES = src/bridge/bridge.c
|
||||
|
||||
libexec_PROGRAMS += cockpit-askpass
|
||||
cockpit_askpass_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS)
|
||||
cockpit_askpass_LDADD = $(libcockpit_bridge_a_LIBS)
|
||||
cockpit_askpass_SOURCES = src/bridge/askpass.c
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# TESTS
|
||||
|
||||
check_PROGRAMS += mock-bridge
|
||||
mock_bridge_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS)
|
||||
mock_bridge_LDADD = $(libcockpit_bridge_a_LIBS)
|
||||
mock_bridge_SOURCES = src/bridge/mock-bridge.c
|
||||
|
||||
TEST_PROGRAM += test-bridge
|
||||
test_bridge_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_bridge_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_bridge_SOURCES = src/bridge/test-bridge.c
|
||||
|
||||
TEST_PROGRAM += test-connect
|
||||
test_connect_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_connect_LDADD = $(test_bridge_LDADD) $(TEST_LIBS)
|
||||
test_connect_SOURCES = src/bridge/test-connect.c
|
||||
|
||||
TEST_PROGRAM += test-dbus-meta
|
||||
test_dbus_meta_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_dbus_meta_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_dbus_meta_SOURCES = src/bridge/test-dbus-meta.c
|
||||
|
||||
TEST_PROGRAM += test-fs
|
||||
test_fs_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_fs_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_fs_SOURCES = src/bridge/test-fs.c
|
||||
|
||||
TEST_PROGRAM += test-httpstream
|
||||
test_httpstream_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_httpstream_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_httpstream_SOURCES = src/bridge/test-httpstream.c
|
||||
|
||||
TEST_PROGRAM += test-metrics
|
||||
test_metrics_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_metrics_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_metrics_SOURCES = src/bridge/test-metrics.c
|
||||
|
||||
TEST_PROGRAM += test-packages
|
||||
test_packages_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_packages_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_packages_SOURCES = src/bridge/test-packages.c
|
||||
|
||||
TEST_PROGRAM += test-packet-channel
|
||||
test_packet_channel_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_packet_channel_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_packet_channel_SOURCES = src/bridge/test-packet-channel.c
|
||||
|
||||
TEST_PROGRAM += test-paths
|
||||
test_paths_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_paths_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_paths_SOURCES = src/bridge/test-paths.c
|
||||
|
||||
TEST_PROGRAM += test-peer
|
||||
test_peer_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_peer_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_peer_SOURCES = src/bridge/test-peer.c
|
||||
|
||||
TEST_PROGRAM += test-pipe-channel
|
||||
test_pipe_channel_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_pipe_channel_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_pipe_channel_SOURCES = src/bridge/test-pipe-channel.c
|
||||
|
||||
TEST_PROGRAM += test-process
|
||||
test_process_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_process_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_process_SOURCES = src/bridge/test-process.c
|
||||
|
||||
TEST_PROGRAM += test-router
|
||||
test_router_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_router_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_router_SOURCES = src/bridge/test-router.c
|
||||
|
||||
TEST_PROGRAM += test-rules
|
||||
test_rules_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_rules_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_rules_SOURCES = src/bridge/test-rules.c
|
||||
|
||||
TEST_PROGRAM += test-stream
|
||||
test_stream_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_stream_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_stream_SOURCES = src/bridge/test-stream.c
|
||||
|
||||
TEST_PROGRAM += test-websocketstream
|
||||
test_websocketstream_CPPFLAGS = $(libcockpit_bridge_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_websocketstream_LDADD = $(libcockpit_bridge_a_LIBS) $(TEST_LIBS)
|
||||
test_websocketstream_SOURCES = src/bridge/test-websocketstream.c
|
||||
|
||||
dist_check_DATA += \
|
||||
src/bridge/mock-resource \
|
||||
src/bridge/mock-pmda.c \
|
||||
src/bridge/mock-pmns \
|
||||
src/bridge/mock-client.crt \
|
||||
src/bridge/mock-client.key \
|
||||
src/bridge/mock-server.crt \
|
||||
src/bridge/mock-server.key \
|
||||
$(NULL)
|
||||
|
||||
endif
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# PCP
|
||||
|
||||
if ENABLE_PCP
|
||||
|
||||
libexec_PROGRAMS += cockpit-pcp
|
||||
|
||||
noinst_LIBRARIES += libcockpit-pcp.a
|
||||
|
||||
libcockpit_pcp_a_CPPFLAGS = \
|
||||
-DG_LOG_DOMAIN=\"cockpit-pcp\" \
|
||||
$(glib_CFLAGS) \
|
||||
$(json_glib_CFLAGS) \
|
||||
$(AM_CPPFLAGS)
|
||||
|
||||
libcockpit_pcp_a_LIBS = \
|
||||
libcockpit-pcp.a \
|
||||
$(libcockpit_metrics_a_LIBS) \
|
||||
-lpcp_pmda -lpcp -lpcp_import \
|
||||
$(NULL)
|
||||
|
||||
libcockpit_pcp_a_SOURCES = \
|
||||
src/bridge/cockpitconnect.c \
|
||||
src/bridge/cockpitconnect.h \
|
||||
src/bridge/cockpitpcpmetrics.c \
|
||||
src/bridge/cockpitpcpmetrics.h \
|
||||
src/bridge/cockpitdbusinternal.c \
|
||||
src/bridge/cockpitdbusinternal.h \
|
||||
src/bridge/cockpitpeer.c \
|
||||
src/bridge/cockpitpeer.h \
|
||||
src/bridge/cockpitrouter.c \
|
||||
src/bridge/cockpitrouter.h \
|
||||
$(libcockpit_bridge_METRICS) \
|
||||
$(NULL)
|
||||
|
||||
cockpit_pcp_CPPFLAGS = $(libcockpit_pcp_a_CPPFLAGS)
|
||||
cockpit_pcp_LDADD = $(libcockpit_pcp_a_LIBS)
|
||||
cockpit_pcp_SOURCES = src/bridge/cockpitpcp.c
|
||||
|
||||
check_PROGRAMS += mock-pmda.so
|
||||
mock_pmda_so_CFLAGS = -fPIC $(AM_CFLAGS)
|
||||
mock_pmda_so_LDFLAGS = -shared
|
||||
mock_pmda_so_LDADD = -lpcp_pmda -lpcp
|
||||
mock_pmda_so_SOURCES = src/bridge/mock-pmda.c
|
||||
|
||||
TEST_PROGRAM += test-pcp
|
||||
test_pcp_CPPFLAGS = $(libcockpit_pcp_a_CFLAGS) $(TEST_CPP)
|
||||
test_pcp_LDADD = $(libcockpit_pcp_a_LIBS) $(TEST_LIBS) -ldl
|
||||
test_pcp_SOURCES = src/bridge/test-pcp.c
|
||||
|
||||
TEST_PROGRAM += test-pcp-archives
|
||||
test_pcp_archives_CPPFLAGS = $(libcockpit_pcp_a_CPPFLAGS) $(TEST_CPP)
|
||||
test_pcp_archives_LDADD = $(libcockpit_pcp_a_LIBS) $(TEST_LIBS)
|
||||
test_pcp_archives_SOURCES = src/bridge/test-pcp-archives.c
|
||||
|
||||
CLEANFILES += mock-archives/*
|
||||
|
||||
endif
|
||||
|
||||
EXTRA_DIST += \
|
||||
src/bridge/cockpit.pam.insecure \
|
||||
$(NULL)
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* Public License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: Stef Walter <stefw@redhat.com>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/cockpitframe.h"
|
||||
#include "common/cockpithex.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitmemory.h"
|
||||
#include "common/cockpittransport.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void
|
||||
byte_array_clear_and_free (gpointer data)
|
||||
{
|
||||
GByteArray *buffer = data;
|
||||
cockpit_memory_clear (buffer->data, buffer->len);
|
||||
g_byte_array_free (buffer, TRUE);
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
read_control_message (int fd)
|
||||
{
|
||||
JsonObject *options = NULL;
|
||||
GBytes *payload = NULL;
|
||||
GBytes *bytes = NULL;
|
||||
gchar *channel = NULL;
|
||||
guchar *data = NULL;
|
||||
gssize length = 0;
|
||||
|
||||
length = cockpit_frame_read (fd, &data);
|
||||
if (length < 0)
|
||||
{
|
||||
g_message ("couldn't read askpass authorize message: %s", g_strerror (errno));
|
||||
length = 0;
|
||||
}
|
||||
else if (length > 0)
|
||||
{
|
||||
/* This could have a password, so clear it when freeing */
|
||||
bytes = g_bytes_new_with_free_func (data, length, byte_array_clear_and_free,
|
||||
g_byte_array_new_take (data, length));
|
||||
payload = cockpit_transport_parse_frame (bytes, &channel);
|
||||
data = NULL;
|
||||
}
|
||||
|
||||
if (payload == NULL)
|
||||
{
|
||||
if (length > 0)
|
||||
g_message ("askpass did not receive valid message");
|
||||
}
|
||||
else if (channel != NULL)
|
||||
{
|
||||
g_message ("askpass did not receive a control message");
|
||||
}
|
||||
else if (!cockpit_transport_parse_command (payload, NULL, NULL, &options))
|
||||
{
|
||||
g_message ("askpass did not receive a valid control message");
|
||||
}
|
||||
|
||||
g_free (channel);
|
||||
|
||||
if (bytes)
|
||||
g_bytes_unref (bytes);
|
||||
if (payload)
|
||||
g_bytes_unref (payload);
|
||||
free (data);
|
||||
return options;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
write_all (int fd,
|
||||
const char *data,
|
||||
gssize len)
|
||||
{
|
||||
gssize res;
|
||||
if (len < 0)
|
||||
len = strlen (data);
|
||||
res = cockpit_fd_write_all (fd, (guchar *)data, len);
|
||||
if (res < 0)
|
||||
{
|
||||
g_message ("couldn't write in askpass: %s", g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
g_debug ("askpass wrote %d bytes", (gint)res);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
write_control_message (int fd,
|
||||
JsonObject *options)
|
||||
{
|
||||
gboolean ret = TRUE;
|
||||
gchar *payload;
|
||||
gchar *prefixed;
|
||||
gsize length;
|
||||
|
||||
payload = cockpit_json_write_object (options, &length);
|
||||
prefixed = g_strdup_printf ("\n%s", payload);
|
||||
if (cockpit_frame_write (fd, (unsigned char *)prefixed, length + 1) < 0)
|
||||
{
|
||||
g_message ("couldn't write authorize message: %s", g_strerror (errno));
|
||||
ret = FALSE;
|
||||
}
|
||||
g_free (prefixed);
|
||||
g_free (payload);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
GOptionContext *context;
|
||||
JsonObject *request = NULL;
|
||||
JsonObject *reply = NULL;
|
||||
GError *error = NULL;
|
||||
const gchar *env;
|
||||
const gchar *command = NULL;
|
||||
const gchar *field = NULL;
|
||||
const gchar *response = NULL;
|
||||
char *user = NULL;
|
||||
gchar *cookie = NULL;
|
||||
gchar *challenge = NULL;
|
||||
gint ret = 1;
|
||||
|
||||
static GOptionEntry entries[] = {
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
context = g_option_context_new (NULL);
|
||||
g_option_context_add_main_entries (context, entries, NULL);
|
||||
g_option_context_set_description (context, "cockpit-bridge uses cockpit-askpass during password prompts.\n");
|
||||
|
||||
g_option_context_parse (context, &argc, &argv, &error);
|
||||
g_option_context_free (context);
|
||||
|
||||
if (error)
|
||||
{
|
||||
g_printerr ("cockpit-askpass: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (isatty (0))
|
||||
{
|
||||
g_printerr ("cockpit-askpass: this command is not meant to be run directly\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't send an init message. This is meant to be used either after an
|
||||
* "init" message has been sent, or with a caller that makes an exception for
|
||||
* the "authorize" command message.
|
||||
*/
|
||||
env = g_getenv ("USER");
|
||||
user = cockpit_hex_encode (env ? env : "", -1);
|
||||
challenge = g_strdup_printf ("plain1:%s:", user);
|
||||
cookie = g_strdup_printf ("askpass%u%u", (unsigned int)getpid (), (unsigned int)time (NULL));
|
||||
|
||||
request = cockpit_transport_build_json ("command", "authorize",
|
||||
"challenge", challenge,
|
||||
"cookie", cookie,
|
||||
"prompt", argv[1],
|
||||
NULL);
|
||||
|
||||
/* Yes, we write to stdin which we expect to be a socketpair() */
|
||||
if (write_control_message (STDIN_FILENO, request))
|
||||
{
|
||||
reply = read_control_message (STDIN_FILENO);
|
||||
if (reply)
|
||||
{
|
||||
if (cockpit_json_get_string (reply, "command", "", &command) &&
|
||||
cockpit_json_get_string (reply, "cookie", "", &field) &&
|
||||
cockpit_json_get_string (reply, "response", "", &response))
|
||||
{
|
||||
if (g_str_equal (field, cookie) && g_str_equal (command, "authorize"))
|
||||
{
|
||||
/* The password is written back on stdout */
|
||||
if (write_all (STDOUT_FILENO, response, -1) && write_all (STDOUT_FILENO, "\n", 1))
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_message ("askpass received unexpected %s control message", command);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_message ("askpass response has invalid control message authorize fields");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_free (cookie);
|
||||
g_free (challenge);
|
||||
free (user);
|
||||
|
||||
/* Clear the password memory owned by JsonObject */
|
||||
if (response)
|
||||
cockpit_memory_clear ((gchar *)response, -1);
|
||||
|
||||
if (request)
|
||||
json_object_unref (request);
|
||||
if (reply)
|
||||
json_object_unref (reply);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,647 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitconnect.h"
|
||||
#include "cockpitdbusinternal.h"
|
||||
#include "cockpitdbusjson.h"
|
||||
#include "cockpitechochannel.h"
|
||||
#include "cockpitfslist.h"
|
||||
#include "cockpitfsread.h"
|
||||
#include "cockpitfswatch.h"
|
||||
#include "cockpitfsreplace.h"
|
||||
#include "cockpithttpstream.h"
|
||||
#include "cockpitinteracttransport.h"
|
||||
#include "cockpitnullchannel.h"
|
||||
#include "cockpitpackages.h"
|
||||
#include "cockpitpacketchannel.h"
|
||||
#include "cockpitpipechannel.h"
|
||||
#include "cockpitinternalmetrics.h"
|
||||
#include "cockpitpolkitagent.h"
|
||||
#include "cockpitrouter.h"
|
||||
#include "cockpitwebsocketstream.h"
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
#include "common/cockpitfdpassing.h"
|
||||
#include "common/cockpithacks-glib.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitpipetransport.h"
|
||||
#include "common/cockpitsystem.h"
|
||||
#include "common/cockpitwebresponse.h"
|
||||
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include <glib-unix.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <gio/gunixsocketaddress.h>
|
||||
|
||||
#include <systemd/sd-journal.h>
|
||||
|
||||
/* This program is run on each managed server, with the credentials
|
||||
of the user that is logged into the Server Console.
|
||||
*/
|
||||
|
||||
static CockpitPackages *packages = NULL;
|
||||
|
||||
static CockpitPayloadType payload_types[] = {
|
||||
{ "dbus-json3", cockpit_dbus_json_get_type },
|
||||
{ "http-stream1", cockpit_http_stream_get_type },
|
||||
{ "http-stream2", cockpit_http_stream_get_type },
|
||||
{ "stream", cockpit_pipe_channel_get_type },
|
||||
{ "packet", cockpit_packet_channel_get_type },
|
||||
{ "fsread1", cockpit_fsread_get_type },
|
||||
{ "fsreplace1", cockpit_fsreplace_get_type },
|
||||
{ "fswatch1", cockpit_fswatch_get_type },
|
||||
{ "fslist1", cockpit_fslist_get_type },
|
||||
{ "null", cockpit_null_channel_get_type },
|
||||
{ "echo", cockpit_echo_channel_get_type },
|
||||
{ "websocket-stream1", cockpit_web_socket_stream_get_type },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static void
|
||||
add_router_channels (CockpitRouter *router)
|
||||
{
|
||||
JsonObject *match;
|
||||
|
||||
match = json_object_new ();
|
||||
json_object_set_string_member (match, "payload", "metrics1");
|
||||
json_object_set_string_member (match, "source", "internal");
|
||||
cockpit_router_add_channel (router, match, cockpit_internal_metrics_get_type);
|
||||
json_object_unref (match);
|
||||
}
|
||||
|
||||
static void
|
||||
on_closed_set_flag (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gboolean *flag = user_data;
|
||||
*flag = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
send_init_command (CockpitTransport *transport,
|
||||
gboolean interactive)
|
||||
{
|
||||
const gchar *checksum;
|
||||
JsonObject *object;
|
||||
JsonObject *block;
|
||||
GHashTable *os_release;
|
||||
gchar **names;
|
||||
GBytes *bytes;
|
||||
gint i;
|
||||
gchar *session_id;
|
||||
|
||||
object = json_object_new ();
|
||||
json_object_set_string_member (object, "command", "init");
|
||||
json_object_set_int_member (object, "version", 1);
|
||||
|
||||
/*
|
||||
* When in interactive mode pretend we received an init
|
||||
* message, and don't print one out.
|
||||
*/
|
||||
if (interactive)
|
||||
{
|
||||
json_object_set_string_member (object, "host", "localhost");
|
||||
}
|
||||
else
|
||||
{
|
||||
checksum = cockpit_packages_get_checksum (packages);
|
||||
if (checksum)
|
||||
json_object_set_string_member (object, "checksum", checksum);
|
||||
|
||||
/* This is encoded as an object to allow for future expansion */
|
||||
block = json_object_new ();
|
||||
names = cockpit_packages_get_names (packages);
|
||||
for (i = 0; names && names[i] != NULL; i++)
|
||||
json_object_set_null_member (block, names[i]);
|
||||
json_object_set_object_member (object, "packages", block);
|
||||
g_free (names);
|
||||
|
||||
os_release = cockpit_system_load_os_release ();
|
||||
if (os_release)
|
||||
{
|
||||
block = cockpit_json_from_hash_table (os_release,
|
||||
cockpit_system_os_release_fields ());
|
||||
if (block)
|
||||
json_object_set_object_member (object, "os-release", block);
|
||||
g_hash_table_unref (os_release);
|
||||
}
|
||||
|
||||
session_id = secure_getenv ("XDG_SESSION_ID");
|
||||
if (session_id)
|
||||
json_object_set_string_member (object, "session-id", session_id);
|
||||
|
||||
block = json_object_new ();
|
||||
json_object_set_boolean_member (block, "explicit-superuser", TRUE);
|
||||
json_object_set_object_member (object, "capabilities", block);
|
||||
}
|
||||
|
||||
bytes = cockpit_json_write_bytes (object);
|
||||
json_object_unref (object);
|
||||
|
||||
if (interactive)
|
||||
cockpit_transport_emit_recv (transport, NULL, bytes);
|
||||
else
|
||||
cockpit_transport_send (transport, NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
static GSubprocess *
|
||||
start_helper_process (const gchar * const *argv,
|
||||
const gchar *socket_pattern,
|
||||
const gchar *socket_envvar)
|
||||
{
|
||||
g_return_val_if_fail (argv[0] != NULL, NULL);
|
||||
|
||||
{
|
||||
const gchar *env = g_getenv (socket_envvar);
|
||||
if (env && env[0])
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_autoptr(GError) error = NULL;
|
||||
/* The DBus daemon produces useless messages on stderr mixed in */
|
||||
g_autoptr(GSubprocessLauncher) launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE |
|
||||
G_SUBPROCESS_FLAGS_STDERR_SILENCE);
|
||||
g_subprocess_launcher_unsetenv (launcher, "G_DEBUG");
|
||||
g_autoptr(GSubprocess) process = g_subprocess_launcher_spawnv (launcher, argv, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT))
|
||||
g_debug ("couldn't start %s: %s", argv[0], error->message);
|
||||
else
|
||||
g_message ("couldn't start %s: %s", argv[0], error->message);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_debug ("launched %s: %s", argv[0], g_subprocess_get_identifier (process));
|
||||
|
||||
/* get the first line of output to figure out the socket address */
|
||||
g_autoptr (GDataInputStream) stream = g_data_input_stream_new (g_subprocess_get_stdout_pipe (process));
|
||||
g_autofree gchar *first_line = g_data_input_stream_read_line (stream, NULL, NULL, &error);
|
||||
if (!first_line)
|
||||
{
|
||||
g_warning ("couldn't read address from %s: %s", argv[0], error->message);
|
||||
g_subprocess_force_exit (process);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_autoptr(GRegex) regex = g_regex_new (socket_pattern, G_REGEX_RAW, 0, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_autoptr(GMatchInfo) info = NULL;
|
||||
if (!g_regex_match (regex, first_line, 0, &info))
|
||||
{
|
||||
g_warning ("output from %s didn't match expected pattern %s", argv[0], socket_pattern);
|
||||
g_subprocess_force_exit (process);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_autofree gchar *socket_address = g_match_info_fetch (info, 1);
|
||||
cockpit_setenv_check (socket_envvar, socket_address, TRUE);
|
||||
|
||||
return g_steal_pointer (&process);
|
||||
}
|
||||
|
||||
static GSubprocess *
|
||||
start_dbus_daemon (void)
|
||||
{
|
||||
const char * const cmd[] = { "dbus-daemon", "--print-address", "--session", NULL };
|
||||
return start_helper_process (cmd, "^(.*)$", "DBUS_SESSION_BUS_ADDRESS");
|
||||
}
|
||||
|
||||
static GSubprocess *
|
||||
start_ssh_agent (void)
|
||||
{
|
||||
const char * const cmd[] = { "ssh-agent", "-s", "-D", NULL };
|
||||
return start_helper_process (cmd, "SSH_AUTH_SOCK=([^;]*);", "SSH_AUTH_SOCK");
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_signal_done (gpointer data)
|
||||
{
|
||||
gboolean *closed = data;
|
||||
*closed = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static struct passwd *
|
||||
getpwuid_a (uid_t uid)
|
||||
{
|
||||
int err;
|
||||
long bufsize = sysconf (_SC_GETPW_R_SIZE_MAX);
|
||||
struct passwd *ret = NULL;
|
||||
struct passwd *buf;
|
||||
|
||||
if (bufsize <= 0)
|
||||
bufsize = 8192;
|
||||
|
||||
buf = g_malloc (sizeof(struct passwd) + bufsize);
|
||||
err = getpwuid_r (uid, buf, (char *)(buf + 1), bufsize, &ret);
|
||||
|
||||
if (ret == NULL)
|
||||
{
|
||||
free (buf);
|
||||
if (err == 0)
|
||||
err = ENOENT;
|
||||
errno = err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
update_router (CockpitRouter *router,
|
||||
gboolean privileged_peer)
|
||||
{
|
||||
if (!privileged_peer)
|
||||
{
|
||||
GList *bridges = cockpit_packages_get_bridges (packages);
|
||||
cockpit_router_set_bridges (router, bridges);
|
||||
g_list_free (bridges);
|
||||
}
|
||||
}
|
||||
|
||||
static CockpitRouter *
|
||||
setup_router (CockpitTransport *transport,
|
||||
gboolean privileged_peer)
|
||||
{
|
||||
CockpitRouter *router = NULL;
|
||||
|
||||
packages = cockpit_packages_new ();
|
||||
|
||||
router = cockpit_router_new (transport, payload_types, NULL);
|
||||
add_router_channels (router);
|
||||
|
||||
/* This has to happen after add_router_channels as the
|
||||
* packages based bridges should have priority.
|
||||
*/
|
||||
update_router (router, privileged_peer);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
struct CallUpdateRouterData {
|
||||
CockpitRouter *router;
|
||||
gboolean privileged_peer;
|
||||
};
|
||||
|
||||
static void
|
||||
call_update_router (gconstpointer user_data)
|
||||
{
|
||||
const struct CallUpdateRouterData *data = user_data;
|
||||
update_router (data->router, data->privileged_peer);
|
||||
}
|
||||
|
||||
static int
|
||||
run_bridge (const gchar *interactive,
|
||||
gboolean privileged_peer)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
CockpitRouter *router;
|
||||
gboolean terminated = FALSE;
|
||||
gboolean interrupted = FALSE;
|
||||
gboolean closed = FALSE;
|
||||
const gchar *directory;
|
||||
struct passwd *pwd;
|
||||
g_autoptr (GSubprocess) dbus_daemon_process = NULL;
|
||||
g_autoptr (GSubprocess) ssh_agent_process = NULL;
|
||||
guint sig_term;
|
||||
guint sig_int;
|
||||
uid_t uid;
|
||||
struct CallUpdateRouterData call_update_router_data;
|
||||
|
||||
cockpit_hacks_redirect_gdebug_to_stderr ();
|
||||
|
||||
/* Always set environment variables early */
|
||||
uid = geteuid();
|
||||
pwd = getpwuid_a (uid);
|
||||
if (pwd == NULL)
|
||||
{
|
||||
g_message ("couldn't get user info: %s", g_strerror (errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
cockpit_setenv_check ("USER", pwd->pw_name, TRUE);
|
||||
cockpit_setenv_check ("HOME", pwd->pw_dir, TRUE);
|
||||
cockpit_setenv_check ("SHELL", pwd->pw_shell, TRUE);
|
||||
}
|
||||
|
||||
/* Set a path if nothing is set */
|
||||
cockpit_setenv_check ("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", FALSE);
|
||||
|
||||
/*
|
||||
* The bridge always runs from within $XDG_RUNTIME_DIR
|
||||
* This makes it easy to create user sockets and/or files.
|
||||
*/
|
||||
if (!privileged_peer)
|
||||
{
|
||||
directory = g_get_user_runtime_dir ();
|
||||
if (g_mkdir_with_parents (directory, 0700) < 0)
|
||||
g_warning ("couldn't create runtime dir: %s: %s", directory, g_strerror (errno));
|
||||
else if (g_chdir (directory) < 0)
|
||||
g_warning ("couldn't change to runtime dir: %s: %s", directory, g_strerror (errno));
|
||||
}
|
||||
|
||||
/* Start daemons if necessary */
|
||||
if (!interactive && !privileged_peer)
|
||||
{
|
||||
dbus_daemon_process = start_dbus_daemon ();
|
||||
ssh_agent_process = start_ssh_agent ();
|
||||
}
|
||||
|
||||
sig_term = g_unix_signal_add (SIGTERM, on_signal_done, &terminated);
|
||||
sig_int = g_unix_signal_add (SIGINT, on_signal_done, &interrupted);
|
||||
|
||||
cockpit_dbus_internal_startup (interactive != NULL);
|
||||
|
||||
if (interactive)
|
||||
{
|
||||
/* Allow skipping the init message when interactive */
|
||||
transport = cockpit_interact_transport_new (0, 1, interactive);
|
||||
}
|
||||
else
|
||||
{
|
||||
transport = cockpit_pipe_transport_new_fds ("stdio", 0, 1);
|
||||
}
|
||||
|
||||
router = setup_router (transport, privileged_peer);
|
||||
|
||||
#ifdef WITH_POLKIT
|
||||
gpointer polkit_agent = NULL;
|
||||
if (uid != 0)
|
||||
{
|
||||
if (!interactive)
|
||||
polkit_agent = cockpit_polkit_agent_register (transport, router, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
cockpit_dbus_user_startup (pwd);
|
||||
cockpit_dbus_process_startup ();
|
||||
cockpit_dbus_machines_startup ();
|
||||
cockpit_dbus_config_startup ();
|
||||
cockpit_packages_dbus_startup (packages);
|
||||
cockpit_dbus_login_messages_startup ();
|
||||
cockpit_router_dbus_startup (router);
|
||||
|
||||
call_update_router_data.router = router;
|
||||
call_update_router_data.privileged_peer = privileged_peer;
|
||||
cockpit_packages_on_change (packages, call_update_router, &call_update_router_data);
|
||||
|
||||
g_free (pwd);
|
||||
pwd = NULL;
|
||||
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
send_init_command (transport, interactive ? TRUE : FALSE);
|
||||
|
||||
while (!terminated && !closed && !interrupted)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
#ifdef WITH_POLKIT
|
||||
if (polkit_agent)
|
||||
cockpit_polkit_agent_unregister (polkit_agent);
|
||||
#endif
|
||||
|
||||
g_object_unref (router);
|
||||
g_object_unref (transport);
|
||||
|
||||
cockpit_packages_on_change (packages, NULL, NULL);
|
||||
|
||||
cockpit_dbus_machines_cleanup ();
|
||||
cockpit_dbus_internal_cleanup ();
|
||||
|
||||
if (dbus_daemon_process)
|
||||
g_subprocess_send_signal (dbus_daemon_process, SIGTERM);
|
||||
if (ssh_agent_process)
|
||||
g_subprocess_send_signal (ssh_agent_process, SIGTERM);
|
||||
|
||||
g_source_remove (sig_term);
|
||||
g_source_remove (sig_int);
|
||||
|
||||
/* HACK: Valgrind contains a bug that causes it to hang when the main
|
||||
* thread exits quickly in response to a signal received by a handler
|
||||
* in another thread, when that other thread is waiting in a syscall.
|
||||
* Avoid that situation by delaying our exit here, but only under
|
||||
* Valgrind.
|
||||
*
|
||||
* Remove this when https://bugs.kde.org/show_bug.cgi?id=409367 is
|
||||
* fixed and widely distributed.
|
||||
*/
|
||||
if (strstr (g_getenv ("LD_PRELOAD") ?: "", "valgrind") != NULL)
|
||||
g_usleep (5 * G_TIME_SPAN_SECOND);
|
||||
|
||||
/* So the caller gets the right signal */
|
||||
if (terminated)
|
||||
raise (SIGTERM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
print_rules (gboolean opt_privileged)
|
||||
{
|
||||
CockpitRouter *router = NULL;
|
||||
CockpitTransport *transport = cockpit_interact_transport_new (0, 1, "--");
|
||||
|
||||
router = setup_router (transport, opt_privileged);
|
||||
|
||||
cockpit_router_dump_rules (router);
|
||||
|
||||
g_object_unref (router);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
print_version (void)
|
||||
{
|
||||
gint i, offset, len;
|
||||
|
||||
g_print ("Version: %s\n", PACKAGE_VERSION);
|
||||
g_print ("Protocol: 1\n");
|
||||
|
||||
g_print ("Payloads: ");
|
||||
offset = 10;
|
||||
for (i = 0; payload_types[i].name != NULL; i++)
|
||||
{
|
||||
len = strlen (payload_types[i].name);
|
||||
if (offset + len > 70)
|
||||
{
|
||||
g_print ("\n");
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
g_print (" ");
|
||||
offset = 4;
|
||||
};
|
||||
|
||||
g_print ("%s ", payload_types[i].name);
|
||||
offset += len + 1;
|
||||
}
|
||||
g_print ("\n");
|
||||
|
||||
g_print ("Authorization: crypt1\n");
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char **argv)
|
||||
{
|
||||
GOptionContext *context;
|
||||
GError *error = NULL;
|
||||
int ret;
|
||||
|
||||
static gboolean opt_packages = FALSE;
|
||||
static gboolean opt_rules = FALSE;
|
||||
static gboolean opt_privileged = FALSE;
|
||||
static gboolean opt_version = FALSE;
|
||||
static gchar *opt_interactive = NULL;
|
||||
|
||||
static GOptionEntry entries[] = {
|
||||
{ "interact", 0, 0, G_OPTION_ARG_STRING, &opt_interactive, "Interact with the raw protocol", "boundary" },
|
||||
{ "privileged", 0, 0, G_OPTION_ARG_NONE, &opt_privileged, "Privileged copy of bridge", NULL },
|
||||
{ "packages", 0, 0, G_OPTION_ARG_NONE, &opt_packages, "Show Cockpit package information", NULL },
|
||||
{ "rules", 0, 0, G_OPTION_ARG_NONE, &opt_rules, "Show Cockpit bridge rules", NULL },
|
||||
{ "version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Show Cockpit version information", NULL },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
signal (SIGPIPE, SIG_IGN);
|
||||
|
||||
if (g_strv_contains ((const gchar **) argv, "--privileged"))
|
||||
{
|
||||
/* We are being spawned, under sudo or pkexec, by the user's copy
|
||||
* of the bridge. In that case, the first thing that will happen,
|
||||
* if we receive our stderr via the socket that is our stdin.
|
||||
*/
|
||||
const char *msg = "\n{\"command\": \"send-stderr\"}";
|
||||
g_print ("%zu\n%s", strlen (msg), msg);
|
||||
|
||||
int parent_stderr_fd;
|
||||
int r = cockpit_socket_receive_fd (STDIN_FILENO, &parent_stderr_fd);
|
||||
if (r == 0)
|
||||
{
|
||||
/* on EOF, just silently exit */
|
||||
return 0;
|
||||
}
|
||||
else if (r == -1)
|
||||
{
|
||||
g_printerr ("cockpit-bridge: recvmsg(stdin) failed: %m\n");
|
||||
return 1;
|
||||
}
|
||||
else if (parent_stderr_fd == -1)
|
||||
{
|
||||
g_printerr ("cockpit-bridge: message from stdin contains no fd\n");
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
r = dup2 (parent_stderr_fd, STDERR_FILENO);
|
||||
g_assert (r == STDERR_FILENO); /* that should really never fail */
|
||||
close (parent_stderr_fd);
|
||||
}
|
||||
}
|
||||
else if (g_getenv ("SSH_CONNECTION") && !g_log_writer_is_journald (2) && ! isatty(2))
|
||||
{
|
||||
/* In case we are run via sshd and we have journald, make sure all
|
||||
* logging output ends up in the journal on *this* machine, not sent
|
||||
* back to the client.
|
||||
*/
|
||||
int fd = sd_journal_stream_fd ("cockpit/ssh", LOG_WARNING, 0);
|
||||
|
||||
/* If it didn't work, then there's no journal. That's OK: we'll
|
||||
* just send the output back to the client after all.
|
||||
*
|
||||
* If it did work, rename the fd to 2 (stderr).
|
||||
*/
|
||||
if (fd >= 0)
|
||||
{
|
||||
int r = dup2 (fd, 2);
|
||||
g_assert (r == 2); /* that should really never fail */
|
||||
close (fd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to tell GLib about an alternate default location for XDG_DATA_DIRS
|
||||
* if we've been compiled with a different prefix. GLib caches that, so need
|
||||
* to do this very early.
|
||||
*/
|
||||
if (!g_getenv ("XDG_DATA_DIRS") && !g_str_equal (DATADIR, "/usr/share"))
|
||||
cockpit_setenv_check ("XDG_DATA_DIRS", DATADIR, TRUE);
|
||||
|
||||
cockpit_setenv_check ("LANG", "C.UTF-8", FALSE);
|
||||
cockpit_setenv_check ("GSETTINGS_BACKEND", "memory", TRUE);
|
||||
|
||||
context = g_option_context_new (NULL);
|
||||
g_option_context_add_main_entries (context, entries, NULL);
|
||||
g_option_context_set_description (context,
|
||||
"cockpit-bridge is run automatically inside of a Cockpit session. When\n"
|
||||
"run from the command line one of the options above must be specified.\n");
|
||||
|
||||
g_option_context_parse (context, &argc, &argv, &error);
|
||||
g_option_context_free (context);
|
||||
|
||||
if (error)
|
||||
{
|
||||
g_printerr ("cockpit-bridge: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (opt_packages)
|
||||
{
|
||||
cockpit_packages_dump ();
|
||||
return 0;
|
||||
}
|
||||
else if (opt_rules)
|
||||
{
|
||||
print_rules (opt_privileged);
|
||||
return 0;
|
||||
}
|
||||
else if (opt_version)
|
||||
{
|
||||
print_version ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!opt_interactive && isatty (1))
|
||||
{
|
||||
g_printerr ("cockpit-bridge: no option specified\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
ret = run_bridge (opt_interactive, opt_privileged);
|
||||
|
||||
if (packages)
|
||||
cockpit_packages_free (packages);
|
||||
|
||||
g_free (opt_interactive);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#%PAM-1.0
|
||||
auth required pam_unix.so
|
||||
|
||||
account required pam_unix.so
|
||||
|
||||
-session required pam_selinux.so close
|
||||
-session optional pam_systemd.so
|
||||
session required pam_unix.so
|
||||
-session required pam_selinux.so open
|
||||
session optional pam_keyinit.so force revoke
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitblocksamples.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
cockpit_block_samples (CockpitSamples *samples)
|
||||
{
|
||||
gchar *contents = NULL;
|
||||
gsize len;
|
||||
GError *error = NULL;
|
||||
gchar **lines = NULL;
|
||||
guint n;
|
||||
static gboolean not_supported = FALSE;
|
||||
|
||||
if (not_supported)
|
||||
goto out;
|
||||
|
||||
if (!g_file_get_contents ("/proc/diskstats", &contents, &len, &error))
|
||||
{
|
||||
g_message ("error loading contents /proc/diskstats: %s", error->message);
|
||||
not_supported = TRUE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
lines = g_strsplit (contents, "\n", -1);
|
||||
for (n = 0; lines != NULL && lines[n] != NULL; n++)
|
||||
{
|
||||
const gchar *line = lines[n];
|
||||
guint num_parsed;
|
||||
gint dev_major, dev_minor;
|
||||
gchar dev_name[128];
|
||||
guint64 num_reads, num_reads_merged, num_sectors_read, num_msec_reading;
|
||||
guint64 num_writes, num_writes_merged, num_sectors_written, num_msec_writing;
|
||||
guint64 num_io_in_progress, num_msec_doing_io, weighted_num_msec_doing_io;
|
||||
|
||||
if (strlen (line) == 0)
|
||||
continue;
|
||||
|
||||
/* From http://www.kernel.org/doc/Documentation/iostats.txt
|
||||
*
|
||||
* Field 1 -- # of reads completed
|
||||
* This is the total number of reads completed successfully.
|
||||
* Field 2 -- # of reads merged, field 6 -- # of writes merged
|
||||
* Reads and writes which are adjacent to each other may be merged for
|
||||
* efficiency. Thus two 4K reads may become one 8K read before it is
|
||||
* ultimately handed to the disk, and so it will be counted (and queued)
|
||||
* as only one I/O. This field lets you know how often this was done.
|
||||
* Field 3 -- # of sectors read
|
||||
* This is the total number of sectors read successfully.
|
||||
* Field 4 -- # of milliseconds spent reading
|
||||
* This is the total number of milliseconds spent by all reads (as
|
||||
* measured from __make_request() to end_that_request_last()).
|
||||
* Field 5 -- # of writes completed
|
||||
* This is the total number of writes completed successfully.
|
||||
* Field 7 -- # of sectors written
|
||||
* This is the total number of sectors written successfully.
|
||||
* Field 8 -- # of milliseconds spent writing
|
||||
* This is the total number of milliseconds spent by all writes (as
|
||||
* measured from __make_request() to end_that_request_last()).
|
||||
* Field 9 -- # of I/Os currently in progress
|
||||
* The only field that should go to zero. Incremented as requests are
|
||||
* given to appropriate struct request_queue and decremented as they finish.
|
||||
* Field 10 -- # of milliseconds spent doing I/Os
|
||||
* This field increases so long as field 9 is nonzero.
|
||||
* Field 11 -- weighted # of milliseconds spent doing I/Os
|
||||
* This field is incremented at each I/O start, I/O completion, I/O
|
||||
* merge, or read of these stats by the number of I/Os in progress
|
||||
* (field 9) times the number of milliseconds spent doing I/O since the
|
||||
* last update of this field. This can provide an easy measure of both
|
||||
* I/O completion time and the backlog that may be accumulating.
|
||||
*/
|
||||
|
||||
num_parsed = sscanf (line,
|
||||
"%d %d %127s"
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT,
|
||||
&dev_major, &dev_minor, dev_name,
|
||||
&num_reads, &num_reads_merged, &num_sectors_read, &num_msec_reading,
|
||||
&num_writes, &num_writes_merged, &num_sectors_written, &num_msec_writing,
|
||||
&num_io_in_progress, &num_msec_doing_io, &weighted_num_msec_doing_io);
|
||||
if (num_parsed != 14)
|
||||
{
|
||||
g_message ("error parsing line %d of file /proc/diskstats (num_parsed = %d): %s", n, num_parsed, line);
|
||||
continue;
|
||||
}
|
||||
|
||||
cockpit_samples_sample (samples, "block.device.read", dev_name, num_sectors_read * 512);
|
||||
cockpit_samples_sample (samples, "block.device.written", dev_name, num_sectors_written * 512);
|
||||
}
|
||||
|
||||
out:
|
||||
g_clear_error (&error);
|
||||
g_strfreev (lines);
|
||||
g_free (contents);
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_BLOCK_SAMPLES_H__
|
||||
#define COCKPIT_BLOCK_SAMPLES_H__
|
||||
|
||||
#include "cockpitsamples.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void cockpit_block_samples (CockpitSamples *samples);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_BLOCK_SAMPLES_H__ */
|
||||
|
|
@ -1,289 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitcgroupsamples.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <fts.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
const char *cockpit_cgroupv1_memory_root = "/sys/fs/cgroup/memory";
|
||||
const char *cockpit_cgroupv1_cpuacct_root = "/sys/fs/cgroup/cpuacct";
|
||||
const char *cockpit_cgroupv2_root = "/sys/fs/cgroup";
|
||||
|
||||
static const char *
|
||||
read_file (int dirfd,
|
||||
char *buf,
|
||||
size_t bufsize,
|
||||
const char *cgroup,
|
||||
const char *fname)
|
||||
{
|
||||
const char *ret = NULL;
|
||||
|
||||
const int fd = openat (dirfd, fname, O_RDONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
if (errno == ENOENT || errno == ENODEV)
|
||||
g_debug ("samples file not found: %s/%s", cgroup, fname);
|
||||
else
|
||||
g_message ("error opening file: %s/%s: %m", cgroup, fname);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* don't do fancy retry/error handling here -- we know what cgroupfs attributes look like,
|
||||
* it's a virtual file system (does not block/no multiple reads), and it's ok to miss
|
||||
* one sample due to EINTR or some race condition */
|
||||
const ssize_t len = read (fd, buf, bufsize);
|
||||
if (len < 0)
|
||||
{
|
||||
if (errno == ENODEV) /* similar to error at open() */
|
||||
g_debug ("error loading file: %s/%s: %m", cgroup, fname);
|
||||
else
|
||||
g_message ("error loading file: %s/%s: %m", cgroup, fname);
|
||||
goto out;
|
||||
}
|
||||
/* we really expect a much smaller read; if we get a full buffer, there's likely
|
||||
* more data, and we are misinterpreting stuff */
|
||||
if (len >= bufsize)
|
||||
{
|
||||
g_warning ("cgroupfs value %s/%s is too large", cgroup, fname);
|
||||
goto out;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
ret = buf;
|
||||
|
||||
out:
|
||||
if (fd >= 0)
|
||||
close (fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static gint64
|
||||
read_int64 (int dirfd,
|
||||
const char *cgroup,
|
||||
const char *fname)
|
||||
{
|
||||
char buf[30];
|
||||
const char *contents = read_file (dirfd, buf, sizeof buf, cgroup, fname);
|
||||
|
||||
if (contents == NULL)
|
||||
return -1;
|
||||
/* no error checking; these often have values like "max" which we want to treat as "invalid/absent" */
|
||||
return atoll(contents);
|
||||
}
|
||||
|
||||
static gint64
|
||||
read_keyed_int64 (int dirfd,
|
||||
const char *cgroup,
|
||||
const char *fname,
|
||||
const char *key)
|
||||
{
|
||||
char buf[256];
|
||||
const char *contents = read_file (dirfd, buf, sizeof buf, cgroup, fname);
|
||||
const char *match;
|
||||
size_t key_len = strlen (key);
|
||||
char *endptr = NULL;
|
||||
gint64 result;
|
||||
|
||||
if (contents == NULL)
|
||||
return -1;
|
||||
|
||||
/* search for a word match of key */
|
||||
match = contents;
|
||||
for (;;)
|
||||
{
|
||||
match = strstr (match, key);
|
||||
if (match == NULL)
|
||||
return -1;
|
||||
/* either matches at start of string, or after a line break */
|
||||
if (match == contents || match[-1] == '\n')
|
||||
break;
|
||||
match += key_len;
|
||||
}
|
||||
|
||||
result = strtoll (match + key_len, &endptr, 10);
|
||||
if (!endptr || (*endptr != '\0' && *endptr != '\n'))
|
||||
{
|
||||
g_warning ("cgroupfs file %s/%s value '%s' is an invalid number", cgroup, fname, contents);
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
collect_memory_v1 (CockpitSamples *samples,
|
||||
int dirfd,
|
||||
const char *cgroup)
|
||||
{
|
||||
gint64 val;
|
||||
|
||||
val = read_int64 (dirfd, cgroup, "memory.usage_in_bytes");
|
||||
if (val > 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.memory.usage", cgroup, val);
|
||||
|
||||
val = read_int64 (dirfd, cgroup, "memory.limit_in_bytes");
|
||||
/* If at max for arch, then unlimited => zero */
|
||||
if (val > 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.memory.limit", cgroup, val);
|
||||
|
||||
val = read_int64 (dirfd, cgroup, "memory.memsw.usage_in_bytes");
|
||||
if (val >= 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.memory.sw-usage", cgroup, val);
|
||||
|
||||
val = read_int64 (dirfd, cgroup, "memory.memsw.limit_in_bytes");
|
||||
/* If at max for arch, then unlimited => zero */
|
||||
if (val > 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.memory.sw-limit", cgroup, val);
|
||||
}
|
||||
|
||||
static void
|
||||
collect_cpu_v1 (CockpitSamples *samples,
|
||||
int dirfd,
|
||||
const char *cgroup)
|
||||
{
|
||||
gint64 val;
|
||||
|
||||
val = read_int64 (dirfd, cgroup, "cpuacct.usage");
|
||||
if (val >= 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.cpu.usage", cgroup, val/1000000);
|
||||
|
||||
val = read_int64 (dirfd, cgroup, "cpu.shares");
|
||||
if (val > 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.cpu.shares", cgroup, val);
|
||||
}
|
||||
|
||||
static void
|
||||
collect_v2 (CockpitSamples *samples,
|
||||
int dirfd,
|
||||
const char *cgroup)
|
||||
{
|
||||
gint64 val;
|
||||
|
||||
/* memory.current: single unsigned value in bytes */
|
||||
val = read_int64 (dirfd, cgroup, "memory.current");
|
||||
if (val >= 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.memory.usage", cgroup, val);
|
||||
|
||||
/* memory.max: literally says "max" if there is no limit set, which ends up as "0" after integer conversion;
|
||||
* only create samples for actually limited cgroups */
|
||||
val = read_int64 (dirfd, cgroup, "memory.max");
|
||||
if (val > 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.memory.limit", cgroup, val);
|
||||
|
||||
/* same as above for swap */
|
||||
val = read_int64 (dirfd, cgroup, "memory.swap.current");
|
||||
if (val >= 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.memory.sw-usage", cgroup, val);
|
||||
|
||||
val = read_int64 (dirfd, cgroup, "memory.swap.max");
|
||||
if (val > 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.memory.sw-limit", cgroup, val);
|
||||
|
||||
/* cpu.weight: only exists if cpu controller is enabled; integer in range [1, 10000] */
|
||||
val = read_int64 (dirfd, cgroup, "cpu.weight");
|
||||
if (val > 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.cpu.shares", cgroup, val);
|
||||
|
||||
/* cpu.stat: keyed file:
|
||||
usage_usec 50000
|
||||
user_usec 40000
|
||||
system_usec 10000
|
||||
*/
|
||||
val = read_keyed_int64 (dirfd, cgroup, "cpu.stat", "usage_usec ");
|
||||
if (val >= 0 && val < G_MAXINT64)
|
||||
cockpit_samples_sample (samples, "cgroup.cpu.usage", cgroup, val/1000);
|
||||
}
|
||||
|
||||
static void
|
||||
notice_cgroups_in_hierarchy (CockpitSamples *samples,
|
||||
const char *root_dir,
|
||||
void (* collect) (CockpitSamples *, int, const char *))
|
||||
{
|
||||
const char *paths[] = { root_dir, NULL };
|
||||
gsize root_dir_len = strlen (root_dir);
|
||||
FTSENT *ent;
|
||||
FTS *fs;
|
||||
|
||||
fs = fts_open ((char **)paths, FTS_NOCHDIR | FTS_COMFOLLOW, NULL);
|
||||
if (fs)
|
||||
{
|
||||
while((ent = fts_read (fs)) != NULL)
|
||||
{
|
||||
if (ent->fts_info == FTS_D)
|
||||
{
|
||||
const char *f = ent->fts_path + root_dir_len;
|
||||
int dfd;
|
||||
|
||||
if (*f == '/')
|
||||
f++;
|
||||
dfd = open (ent->fts_path, O_PATH | O_DIRECTORY);
|
||||
if (dfd >= 0)
|
||||
{
|
||||
collect (samples, dfd, f);
|
||||
close (dfd);
|
||||
}
|
||||
else if (errno != ENOENT)
|
||||
{
|
||||
g_message ("error opening cgroup directory: %s: %m", ent->fts_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
fts_close (fs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
cockpit_cgroup_samples (CockpitSamples *samples)
|
||||
{
|
||||
static int cgroup_ver = 0; /* 0: uninitialized */
|
||||
|
||||
/* do we have cgroupv2? initialize this just once */
|
||||
if (cgroup_ver == 0)
|
||||
{
|
||||
cgroup_ver = (access ("/sys/fs/cgroup/cgroup.controllers", F_OK) == 0) ? 2 : 1;
|
||||
g_debug ("cgroup samples: detected cgroup version: %i", cgroup_ver);
|
||||
}
|
||||
|
||||
if (cgroup_ver == 2)
|
||||
{
|
||||
/* For cgroupv2, the groups are directly in /sys/fs/cgroup/<name>/.../.
|
||||
Inside, we are looking for files "memory.current" or "cpu.stat".
|
||||
*/
|
||||
notice_cgroups_in_hierarchy (samples, cockpit_cgroupv2_root, collect_v2);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* For cgroupv1, we are looking for files like
|
||||
|
||||
/sys/fs/cgroup/memory/.../memory.usage_in_bytes
|
||||
/sys/fs/cgroup/memory/.../memory.limit_in_bytes
|
||||
/sys/fs/cgroup/cpuacct/.../cpuacct.usage
|
||||
*/
|
||||
notice_cgroups_in_hierarchy (samples, cockpit_cgroupv1_memory_root, collect_memory_v1);
|
||||
notice_cgroups_in_hierarchy (samples, cockpit_cgroupv1_cpuacct_root, collect_cpu_v1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_CGROUP_SAMPLES_H__
|
||||
#define COCKPIT_CGROUP_SAMPLES_H__
|
||||
|
||||
#include "cockpitsamples.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void cockpit_cgroup_samples (CockpitSamples *samples);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_CGROUP_SAMPLES_H__ */
|
||||
|
|
@ -1,657 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitconnect.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitloopback.h"
|
||||
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include <gio/gunixsocketaddress.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
const gchar * cockpit_bridge_local_address = NULL;
|
||||
|
||||
CockpitConnectable *
|
||||
cockpit_connectable_ref (CockpitConnectable *connectable)
|
||||
{
|
||||
g_return_val_if_fail (connectable != NULL, NULL);
|
||||
|
||||
if (connectable->refs <= 0)
|
||||
{
|
||||
connectable = g_memdup (connectable, sizeof (CockpitConnectable));
|
||||
g_object_ref (connectable->address);
|
||||
if (connectable->tls_cert)
|
||||
g_object_ref (connectable->tls_cert);
|
||||
if (connectable->tls_database)
|
||||
g_object_ref (connectable->tls_database);
|
||||
connectable->name = g_strdup (connectable->name ? connectable->name : "connect");
|
||||
connectable->refs = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
connectable->refs++;
|
||||
}
|
||||
|
||||
return connectable;
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_connectable_unref (gpointer data)
|
||||
{
|
||||
CockpitConnectable *connectable = data;
|
||||
|
||||
g_return_if_fail (connectable != NULL);
|
||||
g_return_if_fail (connectable->refs > 0);
|
||||
|
||||
if (--connectable->refs == 0)
|
||||
{
|
||||
if (connectable->tls_cert)
|
||||
g_object_unref (connectable->tls_cert);
|
||||
if (connectable->tls_database)
|
||||
g_object_unref (connectable->tls_database);
|
||||
g_object_unref (connectable->address);
|
||||
g_free (connectable->name);
|
||||
g_free (connectable);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
socket_client_event (GSocketClient *client,
|
||||
GSocketClientEvent event,
|
||||
GSocketConnectable *gconnectable,
|
||||
GIOStream *connection,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitConnectable *connectable = user_data;
|
||||
|
||||
switch (event)
|
||||
{
|
||||
case G_SOCKET_CLIENT_TLS_HANDSHAKING:
|
||||
{
|
||||
GTlsClientConnection *tls_client = G_TLS_CLIENT_CONNECTION (connection);
|
||||
|
||||
g_tls_client_connection_set_validation_flags (tls_client, connectable->tls_flags);
|
||||
|
||||
if (connectable->tls_cert)
|
||||
g_tls_connection_set_certificate (G_TLS_CONNECTION (tls_client), connectable->tls_cert);
|
||||
|
||||
if (connectable->tls_database)
|
||||
g_tls_connection_set_database (G_TLS_CONNECTION (tls_client), connectable->tls_database);
|
||||
|
||||
g_tls_connection_set_require_close_notify (G_TLS_CONNECTION (tls_client), FALSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
socket_client_connect_done (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = user_data;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
g_autoptr(GSocketConnection) connection = g_socket_client_connect_finish (G_SOCKET_CLIENT (object), result, &error);
|
||||
if (connection != NULL)
|
||||
{
|
||||
GIOStream *stream; /* weak */
|
||||
|
||||
if (G_IS_TCP_WRAPPER_CONNECTION (connection))
|
||||
{
|
||||
stream = g_tcp_wrapper_connection_get_base_io_stream (G_TCP_WRAPPER_CONNECTION (connection));
|
||||
g_assert (G_IS_TLS_CONNECTION (stream));
|
||||
}
|
||||
else
|
||||
stream = G_IO_STREAM (connection);
|
||||
|
||||
g_task_return_pointer (task, g_object_ref (stream), g_object_unref);
|
||||
}
|
||||
else
|
||||
g_task_return_error (task, g_steal_pointer (&error));
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_connect_stream (GSocketConnectable *address,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitConnectable connectable = { .address = address };
|
||||
|
||||
g_return_if_fail (G_IS_SOCKET_CONNECTABLE (address));
|
||||
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
cockpit_connect_stream_full (&connectable, cancellable, callback, user_data);
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_connect_stream_full (CockpitConnectable *connectable,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_return_if_fail (connectable != NULL);
|
||||
g_return_if_fail (G_IS_SOCKET_CONNECTABLE (connectable->address));
|
||||
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
g_autoptr(GSocketClient) client = g_socket_client_new ();
|
||||
|
||||
/* otherwise, we'll go fishing around in GSettings... */
|
||||
g_socket_client_set_enable_proxy (client, FALSE);
|
||||
|
||||
if (connectable->tls)
|
||||
{
|
||||
g_signal_connect_data (client, "event", G_CALLBACK (socket_client_event),
|
||||
cockpit_connectable_ref (connectable),
|
||||
(GClosureNotify) cockpit_connectable_unref, 0);
|
||||
|
||||
g_socket_client_set_tls (client, TRUE);
|
||||
}
|
||||
|
||||
g_socket_client_connect_async (client, connectable->address, cancellable, socket_client_connect_done,
|
||||
g_task_new (NULL, cancellable, callback, user_data));
|
||||
}
|
||||
|
||||
GIOStream *
|
||||
cockpit_connect_stream_finish (GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
|
||||
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
static GSocketConnectable *
|
||||
parse_address (CockpitChannel *channel,
|
||||
gchar **possible_name,
|
||||
gboolean *local_address)
|
||||
{
|
||||
GSocketConnectable *connectable = NULL;
|
||||
const gchar *unix_path;
|
||||
const gchar *address;
|
||||
JsonObject *options;
|
||||
gboolean local = FALSE;
|
||||
GError *error = NULL;
|
||||
const gchar *host;
|
||||
gint64 port;
|
||||
gchar *name = NULL;
|
||||
gboolean open = FALSE;
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
if (!cockpit_json_get_string (options, "unix", NULL, &unix_path))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"unix\" option in channel");
|
||||
goto out;
|
||||
}
|
||||
if (!cockpit_json_get_int (options, "port", G_MAXINT64, &port))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"port\" option in channel");
|
||||
goto out;
|
||||
}
|
||||
if (!cockpit_json_get_string (options, "address", NULL, &address))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"address\" option in channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (port != G_MAXINT64 && unix_path)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "cannot specify both \"port\" and \"unix\" options");
|
||||
goto out;
|
||||
}
|
||||
else if (port != G_MAXINT64)
|
||||
{
|
||||
if (port <= 0 || port > 65535)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "received invalid \"port\" option");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (address)
|
||||
{
|
||||
connectable = g_network_address_new (address, port);
|
||||
host = address;
|
||||
|
||||
/* This isn't perfect, but matches the use case. Specify address => non-local */
|
||||
local = FALSE;
|
||||
}
|
||||
else if (cockpit_bridge_local_address)
|
||||
{
|
||||
connectable = g_network_address_parse (cockpit_bridge_local_address, port, &error);
|
||||
host = cockpit_bridge_local_address;
|
||||
local = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
connectable = cockpit_loopback_new (port);
|
||||
host = "localhost";
|
||||
local = TRUE;
|
||||
}
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"couldn't parse local address: %s: %s", host, error->message);
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = g_strdup_printf ("%s:%d", host, (gint)port);
|
||||
}
|
||||
}
|
||||
else if (unix_path)
|
||||
{
|
||||
name = g_strdup (unix_path);
|
||||
connectable = G_SOCKET_CONNECTABLE (g_unix_socket_address_new (unix_path));
|
||||
local = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"no \"port\" or \"unix\" or other address option for channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
open = TRUE;
|
||||
|
||||
out:
|
||||
g_clear_error (&error);
|
||||
if (open)
|
||||
{
|
||||
if (possible_name)
|
||||
*possible_name = g_strdup (name);
|
||||
if (local_address)
|
||||
*local_address = local;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (connectable)
|
||||
g_object_unref (connectable);
|
||||
connectable = NULL;
|
||||
}
|
||||
|
||||
g_free (name);
|
||||
return connectable;
|
||||
}
|
||||
|
||||
GSocketAddress *
|
||||
cockpit_connect_parse_address (CockpitChannel *channel,
|
||||
gchar **possible_name)
|
||||
{
|
||||
GSocketAddress *address;
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autofree gchar *name = NULL;
|
||||
|
||||
g_autoptr(GSocketConnectable) connectable = parse_address (channel, &name, NULL);
|
||||
if (!connectable)
|
||||
return NULL;
|
||||
|
||||
/* This is sync, but realistically, it doesn't matter for current use cases */
|
||||
g_autoptr(GSocketAddressEnumerator) enumerator = g_socket_connectable_enumerate (connectable);
|
||||
|
||||
address = g_socket_address_enumerator_next (enumerator, NULL, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
cockpit_channel_fail (channel, "not-found", "couldn't find address: %s: %s", name, error->message);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (possible_name)
|
||||
*possible_name = g_steal_pointer (&name);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_option_file_or_data (CockpitChannel *channel,
|
||||
JsonObject *options,
|
||||
const gchar *option,
|
||||
const gchar **file,
|
||||
const gchar **data)
|
||||
{
|
||||
JsonObject *object;
|
||||
JsonNode *node;
|
||||
|
||||
g_assert (file != NULL);
|
||||
g_assert (data != NULL);
|
||||
|
||||
node = json_object_get_member (options, option);
|
||||
if (!node)
|
||||
{
|
||||
*file = NULL;
|
||||
*data = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!JSON_NODE_HOLDS_OBJECT (node))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"%s\" tls option for channel", option);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
object = json_node_get_object (node);
|
||||
|
||||
if (!cockpit_json_get_string (object, "file", NULL, file))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"file\" %s option for channel", option);
|
||||
}
|
||||
else if (!cockpit_json_get_string (object, "data", NULL, data))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"data\" %s option for channel", option);
|
||||
}
|
||||
else if (!*file && !*data)
|
||||
{
|
||||
cockpit_channel_fail (channel, "not-supported", "missing or unsupported \"%s\" option for channel", option);
|
||||
}
|
||||
else if (*file && *data)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "cannot specify both \"file\" and \"data\" in \"%s\" option for channel", option);
|
||||
}
|
||||
else
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
load_pem_contents (CockpitChannel *channel,
|
||||
const gchar *filename,
|
||||
const gchar *option,
|
||||
GString *pem)
|
||||
{
|
||||
GError *error = NULL;
|
||||
gchar *contents = NULL;
|
||||
gsize len;
|
||||
|
||||
if (!g_file_get_contents (filename, &contents, &len, &error))
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"couldn't load \"%s\" file: %s: %s", option, filename, error->message);
|
||||
g_clear_error (&error);
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_string_append_len (pem, contents, len);
|
||||
g_string_append_c (pem, '\n');
|
||||
g_free (contents);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static gchar *
|
||||
expand_filename (const gchar *filename)
|
||||
{
|
||||
if (!g_path_is_absolute (filename))
|
||||
return g_build_filename (g_get_home_dir (), filename, NULL);
|
||||
else
|
||||
return g_strdup (filename);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_cert_option_as_pem (CockpitChannel *channel,
|
||||
JsonObject *options,
|
||||
const gchar *option,
|
||||
GString *pem)
|
||||
{
|
||||
gboolean ret = TRUE;
|
||||
const gchar *file;
|
||||
const gchar *data;
|
||||
gchar *path;
|
||||
|
||||
if (!parse_option_file_or_data (channel, options, option, &file, &data))
|
||||
return FALSE;
|
||||
|
||||
if (file)
|
||||
{
|
||||
path = expand_filename (file);
|
||||
|
||||
/* For now we assume file contents are PEM */
|
||||
ret = load_pem_contents (channel, path, option, pem);
|
||||
|
||||
g_free (path);
|
||||
}
|
||||
else if (data)
|
||||
{
|
||||
/* Format this as PEM of the given type */
|
||||
g_string_append (pem, data);
|
||||
g_string_append_c (pem, '\n');
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_cert_option_as_database (CockpitChannel *channel,
|
||||
JsonObject *options,
|
||||
const gchar *option,
|
||||
GTlsDatabase **database)
|
||||
{
|
||||
gboolean temporary = FALSE;
|
||||
GError *error = NULL;
|
||||
gboolean ret = TRUE;
|
||||
const gchar *file;
|
||||
const gchar *data;
|
||||
gchar *path;
|
||||
gint fd;
|
||||
|
||||
if (!parse_option_file_or_data (channel, options, option, &file, &data))
|
||||
return FALSE;
|
||||
|
||||
if (file)
|
||||
{
|
||||
path = expand_filename (file);
|
||||
ret = TRUE;
|
||||
}
|
||||
else if (data)
|
||||
{
|
||||
temporary = TRUE;
|
||||
path = g_build_filename (g_get_user_runtime_dir (), "cockpit-bridge-cert-authority.XXXXXX", NULL);
|
||||
fd = g_mkstemp (path);
|
||||
if (fd < 0)
|
||||
{
|
||||
ret = FALSE;
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"couldn't create temporary directory: %s: %s", path, g_strerror (errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
close (fd);
|
||||
if (!g_file_set_contents (path, data, -1, &error))
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"couldn't write temporary data to: %s: %s", path, error->message);
|
||||
g_clear_error (&error);
|
||||
ret = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Not specified */
|
||||
*database = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
{
|
||||
*database = g_tls_file_database_new (path, &error);
|
||||
if (error)
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"couldn't load certificate data: %s: %s", path, error->message);
|
||||
g_clear_error (&error);
|
||||
ret = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Leave around when problem, for debugging */
|
||||
if (temporary && ret == TRUE)
|
||||
g_unlink (path);
|
||||
|
||||
g_free (path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_stream_options (CockpitChannel *channel,
|
||||
CockpitConnectable *connectable)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
GTlsCertificate *cert = NULL;
|
||||
GTlsDatabase *database = NULL;
|
||||
gboolean use_tls = FALSE;
|
||||
GError *error = NULL;
|
||||
GString *pem = NULL;
|
||||
JsonObject *options;
|
||||
JsonNode *node;
|
||||
|
||||
/* No validation for local servers by default */
|
||||
gboolean validate = !connectable->local;
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
node = json_object_get_member (options, "tls");
|
||||
if (node && !JSON_NODE_HOLDS_OBJECT (node))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"tls\" option for channel");
|
||||
goto out;
|
||||
}
|
||||
else if (node)
|
||||
{
|
||||
options = json_node_get_object (node);
|
||||
use_tls = TRUE;
|
||||
|
||||
/*
|
||||
* The only function in GLib to parse private keys takes
|
||||
* them in PEM concatenated form. This is a limitation of GLib,
|
||||
* rather than concatenated form being a decent standard for
|
||||
* certificates and keys. So build a combined PEM as expected by
|
||||
* GLib here.
|
||||
*/
|
||||
|
||||
pem = g_string_sized_new (8192);
|
||||
|
||||
if (!parse_cert_option_as_pem (channel, options, "certificate", pem))
|
||||
goto out;
|
||||
|
||||
if (pem->len)
|
||||
{
|
||||
if (!parse_cert_option_as_pem (channel, options, "key", pem))
|
||||
goto out;
|
||||
|
||||
cert = g_tls_certificate_new_from_pem (pem->str, pem->len, &error);
|
||||
if (error != NULL)
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"invalid \"certificate\" or \"key\" content: %s", error->message);
|
||||
g_error_free (error);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parse_cert_option_as_database (channel, options, "authority", &database))
|
||||
goto out;
|
||||
|
||||
if (!cockpit_json_get_bool (options, "validate", validate, &validate))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"validate\" option");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
|
||||
out:
|
||||
if (ret)
|
||||
{
|
||||
connectable->tls = use_tls;
|
||||
connectable->tls_cert = cert;
|
||||
cert = NULL;
|
||||
|
||||
if (database)
|
||||
{
|
||||
connectable->tls_database = database;
|
||||
connectable->tls_flags = G_TLS_CERTIFICATE_VALIDATE_ALL;
|
||||
if (!validate)
|
||||
connectable->tls_flags &= ~(G_TLS_CERTIFICATE_INSECURE | G_TLS_CERTIFICATE_BAD_IDENTITY);
|
||||
database = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (validate)
|
||||
connectable->tls_flags = G_TLS_CERTIFICATE_VALIDATE_ALL;
|
||||
else
|
||||
connectable->tls_flags = G_TLS_CERTIFICATE_GENERIC_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (pem)
|
||||
g_string_free (pem, TRUE);
|
||||
if (cert)
|
||||
g_object_unref (cert);
|
||||
if (database)
|
||||
g_object_unref (database);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CockpitConnectable *
|
||||
cockpit_connect_parse_stream (CockpitChannel *channel)
|
||||
{
|
||||
CockpitConnectable *connectable;
|
||||
GSocketConnectable *address;
|
||||
gboolean local = FALSE;
|
||||
gchar *name = NULL;
|
||||
|
||||
address = parse_address (channel, &name, &local);
|
||||
if (!address)
|
||||
{
|
||||
g_free (name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
connectable = g_new0 (CockpitConnectable, 1);
|
||||
connectable->address = address;
|
||||
connectable->name = name;
|
||||
connectable->refs = 1;
|
||||
connectable->local = local;
|
||||
|
||||
if (!parse_stream_options (channel, connectable))
|
||||
{
|
||||
cockpit_connectable_unref (connectable);
|
||||
connectable = NULL;
|
||||
}
|
||||
|
||||
return connectable;
|
||||
}
|
||||
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __COCKPIT_CONNECT_H__
|
||||
#define __COCKPIT_CONNECT_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct {
|
||||
gint refs;
|
||||
gchar *name;
|
||||
|
||||
/* Where to connect to */
|
||||
GSocketConnectable *address;
|
||||
|
||||
/* TLS flags */
|
||||
gboolean tls;
|
||||
gboolean local;
|
||||
GTlsCertificateFlags tls_flags;
|
||||
GTlsCertificate *tls_cert;
|
||||
GTlsDatabase *tls_database;
|
||||
} CockpitConnectable;
|
||||
|
||||
CockpitConnectable * cockpit_connectable_ref (CockpitConnectable *connectable);
|
||||
|
||||
void cockpit_connectable_unref (gpointer data);
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(CockpitConnectable, cockpit_connectable_unref)
|
||||
|
||||
void cockpit_connect_stream (GSocketConnectable *address,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
void cockpit_connect_stream_full (CockpitConnectable *connectable,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
GIOStream * cockpit_connect_stream_finish (GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
CockpitConnectable * cockpit_connect_parse_stream (CockpitChannel *self);
|
||||
|
||||
GSocketAddress * cockpit_connect_parse_address (CockpitChannel *self,
|
||||
gchar **possible_name);
|
||||
|
||||
void cockpit_connect_add_internal_address (const gchar *name,
|
||||
GSocketAddress *address);
|
||||
|
||||
gboolean cockpit_connect_remove_internal_address (const gchar *name);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __COCKPIT_STREAM_H__ */
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitcpusamples.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define CPU_CORE_MAXLEN 8
|
||||
|
||||
gint cockpit_cpu_user_hz = -1;
|
||||
|
||||
static gint
|
||||
ensure_user_hz (void)
|
||||
{
|
||||
if (cockpit_cpu_user_hz <= 0)
|
||||
{
|
||||
cockpit_cpu_user_hz = sysconf (_SC_CLK_TCK);
|
||||
if (cockpit_cpu_user_hz == -1 || cockpit_cpu_user_hz == 0)
|
||||
{
|
||||
g_warning ("sysconf (_SC_CLK_TCK) returned %d - forcing user_hz to 100", cockpit_cpu_user_hz);
|
||||
cockpit_cpu_user_hz = 100;
|
||||
}
|
||||
}
|
||||
return cockpit_cpu_user_hz;
|
||||
}
|
||||
|
||||
/* TODO: this should be optimized so we don't allocate memory and call open()/close() all the time */
|
||||
|
||||
void
|
||||
cockpit_cpu_samples (CockpitSamples *samples)
|
||||
{
|
||||
gchar *contents = NULL;
|
||||
GError *error = NULL;
|
||||
gchar **lines = NULL;
|
||||
guint64 user_hz;
|
||||
gsize len;
|
||||
guint n;
|
||||
|
||||
if (!g_file_get_contents ("/proc/stat", &contents, &len, &error))
|
||||
{
|
||||
g_message ("error loading contents /proc/stat: %s", error->message);
|
||||
g_error_free (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* see 'man proc' for the format of /proc/stat */
|
||||
|
||||
lines = g_strsplit (contents, "\n", -1);
|
||||
for (n = 0; lines != NULL && lines[n] != NULL; n++)
|
||||
{
|
||||
const gchar *line = lines[n];
|
||||
guint64 user;
|
||||
guint64 nice;
|
||||
guint64 system;
|
||||
guint64 idle;
|
||||
guint64 iowait;
|
||||
gchar cpu_core[CPU_CORE_MAXLEN + 1];
|
||||
|
||||
if (!(g_str_has_prefix (line, "cpu")))
|
||||
continue;
|
||||
|
||||
#define FMT64 "%" G_GUINT64_FORMAT " "
|
||||
if (sscanf (line, "%" G_STRINGIFY (CPU_CORE_MAXLEN) "s " FMT64 FMT64 FMT64 FMT64 FMT64,
|
||||
cpu_core,
|
||||
&user,
|
||||
&nice,
|
||||
&system,
|
||||
&idle,
|
||||
&iowait) != 6)
|
||||
{
|
||||
g_warning ("Error parsing line %d of /proc/stat with content `%s'", n, line);
|
||||
continue;
|
||||
}
|
||||
|
||||
user_hz = ensure_user_hz ();
|
||||
if (strlen (cpu_core) > 3)
|
||||
{
|
||||
cockpit_samples_sample (samples, "cpu.core.nice", cpu_core + 3, nice*1000/user_hz);
|
||||
cockpit_samples_sample (samples, "cpu.core.user", cpu_core + 3, user*1000/user_hz);
|
||||
cockpit_samples_sample (samples, "cpu.core.system", cpu_core + 3, system*1000/user_hz);
|
||||
cockpit_samples_sample (samples, "cpu.core.iowait", cpu_core + 3, iowait*1000/user_hz);
|
||||
}
|
||||
else
|
||||
{
|
||||
cockpit_samples_sample (samples, "cpu.basic.nice", NULL, nice*1000/user_hz);
|
||||
cockpit_samples_sample (samples, "cpu.basic.user", NULL, user*1000/user_hz);
|
||||
cockpit_samples_sample (samples, "cpu.basic.system", NULL, system*1000/user_hz);
|
||||
cockpit_samples_sample (samples, "cpu.basic.iowait", NULL, iowait*1000/user_hz);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
g_strfreev (lines);
|
||||
g_free (contents);
|
||||
}
|
||||
|
||||
static gchar*
|
||||
read_file (const gchar *path)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
gchar *content = NULL;
|
||||
|
||||
if (!g_file_get_contents (path, &content, NULL, &error))
|
||||
{
|
||||
// ENOENT is used to break loops, do not log it
|
||||
if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
|
||||
g_warning ("error reading file: %s", error->message);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
static void
|
||||
sample_cpu_sensors (gchar *sensor_path,
|
||||
CockpitSamples *samples)
|
||||
{
|
||||
g_autofree gchar *temp_content = read_file (sensor_path);
|
||||
|
||||
if (temp_content == NULL)
|
||||
return;
|
||||
|
||||
gint64 temperature = g_ascii_strtoll (temp_content, NULL, 10);
|
||||
// overflow or invalid
|
||||
if (temperature == G_MAXINT64 || temperature == G_MININT64 || temperature == 0)
|
||||
{
|
||||
g_debug("Invalid number in %s: %m", sensor_path);
|
||||
return;
|
||||
}
|
||||
|
||||
cockpit_samples_sample (samples, "cpu.temperature", sensor_path, temperature/1000);
|
||||
}
|
||||
|
||||
static void
|
||||
detect_cpu_sensors (GList **devices,
|
||||
const gchar *hwmon_name,
|
||||
int hwmonID)
|
||||
{
|
||||
g_autofree gchar *path = g_strdup_printf ("/sys/class/hwmon/hwmon%d", hwmonID);
|
||||
g_autoptr(GDir) dir = g_dir_open (path, 0, NULL);
|
||||
if (dir == NULL)
|
||||
return;
|
||||
|
||||
|
||||
const char *name;
|
||||
while ((name = g_dir_read_name (dir)))
|
||||
{
|
||||
uint i;
|
||||
if (!g_str_has_suffix (name, "_input") || (sscanf(name, "temp%d_input", &i) != 1))
|
||||
continue;
|
||||
|
||||
g_autofree char *sensor_path = g_build_filename (path, name, NULL);
|
||||
g_autofree gchar *label = g_strdup_printf ("/sys/class/hwmon/hwmon%d/temp%d_label", hwmonID, i);
|
||||
g_autofree gchar *label_content = read_file (label);
|
||||
|
||||
if (label_content == NULL)
|
||||
{
|
||||
// labels aren't used on ARM
|
||||
if (!g_str_equal (hwmon_name, "cpu_thermal"))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_strchomp (label_content);
|
||||
|
||||
// only sample CPU Temperature in atk0110
|
||||
if (!g_str_equal (label_content, "CPU Temperature") && g_str_equal (hwmon_name, "atk0110"))
|
||||
continue;
|
||||
}
|
||||
|
||||
*devices = g_list_append (*devices, g_steal_pointer (&sensor_path));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
detect_hwmon_device (GList **devices)
|
||||
{
|
||||
// iterate through all hwmon folders to find CPU sensors
|
||||
for (int i = 0; TRUE; i++)
|
||||
{
|
||||
g_autofree gchar *path = g_strdup_printf ("/sys/class/hwmon/hwmon%d/name", i);
|
||||
g_autofree gchar *name = read_file (path);
|
||||
|
||||
// end loop on error
|
||||
if (name == NULL)
|
||||
break;
|
||||
|
||||
g_strchomp (name);
|
||||
|
||||
// compare device name with CPU names
|
||||
// Intel: coretemp, AMD: k8temp or k10temp, ARM: cpu_thermal, Asus motherboard: atk0110
|
||||
if (g_str_equal (name, "coretemp") || g_str_equal (name, "cpu_thermal") ||
|
||||
g_str_equal (name, "k8temp") || g_str_equal (name, "k10temp") || g_str_equal (name, "atk0110"))
|
||||
{
|
||||
// hwmon contains CPU info
|
||||
detect_cpu_sensors (devices, name, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_cpu_temperature (CockpitSamples *samples)
|
||||
{
|
||||
static GList *devices = NULL;
|
||||
static gboolean initialized = FALSE;
|
||||
if (!initialized)
|
||||
{
|
||||
detect_hwmon_device (&devices);
|
||||
initialized = TRUE;
|
||||
}
|
||||
|
||||
g_list_foreach (devices, (GFunc)sample_cpu_sensors, samples);
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_CPU_SAMPLES_H__
|
||||
#define COCKPIT_CPU_SAMPLES_H__
|
||||
|
||||
#include "cockpitsamples.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void cockpit_cpu_samples (CockpitSamples *samples);
|
||||
|
||||
void cockpit_cpu_temperature (CockpitSamples *samples);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_CPU_SAMPLES_H__ */
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __COCKPIT_DBUS_CACHE_H
|
||||
#define __COCKPIT_DBUS_CACHE_H
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_DBUS_CACHE (cockpit_dbus_cache_get_type ())
|
||||
#define COCKPIT_DBUS_CACHE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_DBUS_CACHE, CockpitDBusCache))
|
||||
#define COCKPIT_IS_DBUS_CACHE(k) (G_TYPE_CHECK_INSTANCE_TYPE ((k), COCKPIT_TYPE_DBUS_CACHE))
|
||||
|
||||
typedef struct _CockpitDBusCache CockpitDBusCache;
|
||||
typedef struct _CockpitDBusCacheClass CockpitDBusCacheClass;
|
||||
|
||||
struct _CockpitDBusCacheClass {
|
||||
GObjectClass parent;
|
||||
|
||||
void (* meta) (CockpitDBusCache *self,
|
||||
GDBusInterfaceInfo *iface);
|
||||
|
||||
void (* update) (CockpitDBusCache *self,
|
||||
const gchar *path,
|
||||
GDBusInterfaceInfo *iface);
|
||||
};
|
||||
|
||||
typedef void (* CockpitDBusIntrospectFunc) (CockpitDBusCache *cache,
|
||||
GDBusInterfaceInfo *iface,
|
||||
gpointer user_data);
|
||||
|
||||
typedef void (* CockpitDBusBarrierFunc) (CockpitDBusCache *cache,
|
||||
gpointer user_data);
|
||||
|
||||
GType cockpit_dbus_cache_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitDBusCache * cockpit_dbus_cache_new (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
const gchar *logname,
|
||||
GHashTable *interface_info);
|
||||
|
||||
void cockpit_dbus_cache_barrier (CockpitDBusCache *self,
|
||||
CockpitDBusBarrierFunc callback,
|
||||
gpointer user_data);
|
||||
|
||||
void cockpit_dbus_cache_poke (CockpitDBusCache *self,
|
||||
const gchar *path,
|
||||
const gchar *iface);
|
||||
|
||||
void cockpit_dbus_cache_scrape (CockpitDBusCache *self,
|
||||
GVariant *data);
|
||||
|
||||
void cockpit_dbus_cache_watch (CockpitDBusCache *self,
|
||||
const gchar *path,
|
||||
gboolean is_namespace,
|
||||
const gchar *interface);
|
||||
|
||||
gboolean cockpit_dbus_cache_unwatch (CockpitDBusCache *self,
|
||||
const gchar *path,
|
||||
gboolean is_namespace,
|
||||
const gchar *interface);
|
||||
|
||||
void cockpit_dbus_cache_introspect (CockpitDBusCache *self,
|
||||
const gchar *path,
|
||||
const gchar *interface,
|
||||
CockpitDBusIntrospectFunc callback,
|
||||
gpointer user_data);
|
||||
|
||||
void cockpit_dbus_cache_set_name_owner (CockpitDBusCache *self,
|
||||
const gchar *name_owner);
|
||||
|
||||
|
||||
GHashTable * cockpit_dbus_interface_info_new (void);
|
||||
|
||||
GDBusInterfaceInfo * cockpit_dbus_interface_info_lookup (GHashTable *interface_info,
|
||||
const gchar *interface_name);
|
||||
|
||||
void cockpit_dbus_interface_info_push (GHashTable *interface_info,
|
||||
GDBusInterfaceInfo *interface);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __COCKPIT_DBUS_CACHE_H */
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusinternal.h"
|
||||
#include "common/cockpitconf.h"
|
||||
|
||||
static void
|
||||
config_method_call (GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *method_name,
|
||||
GVariant *parameters,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gpointer user_data)
|
||||
{
|
||||
if (g_str_equal (method_name, "Reload"))
|
||||
{
|
||||
cockpit_conf_cleanup ();
|
||||
g_dbus_method_invocation_return_value (invocation, NULL);
|
||||
}
|
||||
else if (g_str_equal (method_name, "GetString"))
|
||||
{
|
||||
const gchar *section, *key;
|
||||
const char *result;
|
||||
|
||||
g_variant_get (parameters, "(&s&s)", §ion, &key);
|
||||
|
||||
result = cockpit_conf_string (section, key);
|
||||
if (result)
|
||||
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", result));
|
||||
else
|
||||
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
|
||||
"key '%s' in section '%s' does not exist",
|
||||
key, section);
|
||||
}
|
||||
else if (g_str_equal (method_name, "GetUInt"))
|
||||
{
|
||||
const gchar *section, *key;
|
||||
unsigned _default, max, min;
|
||||
unsigned result;
|
||||
|
||||
g_variant_get (parameters, "(&s&suuu)", §ion, &key, &_default, &max, &min);
|
||||
result = cockpit_conf_uint (section, key, _default, max, min);
|
||||
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", result));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_return_if_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static GDBusInterfaceVTable config_vtable = {
|
||||
.method_call = config_method_call,
|
||||
};
|
||||
|
||||
static GDBusArgInfo config_section_arg = {
|
||||
-1, "section", "s", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo config_key_arg = {
|
||||
-1, "key", "s", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo config_default_uint_arg = {
|
||||
-1, "default", "u", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo config_max_uint_arg = {
|
||||
-1, "max", "u", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo config_min_uint_arg = {
|
||||
-1, "min", "u", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo *config_getstring_in_args[] = {
|
||||
&config_section_arg,
|
||||
&config_key_arg,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo config_string_value_arg = {
|
||||
-1, "value", "s", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo *config_string_value_out_args[] = {
|
||||
&config_string_value_arg,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo config_uint_value_arg = {
|
||||
-1, "value", "u", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo *config_uint_value_out_args[] = {
|
||||
&config_uint_value_arg,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo *config_getuint_in_args[] = {
|
||||
&config_section_arg,
|
||||
&config_key_arg,
|
||||
&config_default_uint_arg,
|
||||
&config_max_uint_arg,
|
||||
&config_min_uint_arg,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusMethodInfo config_reload_method = {
|
||||
-1, "Reload", NULL, NULL, NULL
|
||||
};
|
||||
|
||||
static GDBusMethodInfo config_getstring_method = {
|
||||
-1, "GetString", config_getstring_in_args, config_string_value_out_args, NULL
|
||||
};
|
||||
|
||||
static GDBusMethodInfo config_getuint_method = {
|
||||
-1, "GetUInt", config_getuint_in_args, config_uint_value_out_args, NULL
|
||||
};
|
||||
|
||||
static GDBusMethodInfo *config_methods[] = {
|
||||
&config_reload_method,
|
||||
&config_getstring_method,
|
||||
&config_getuint_method,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusInterfaceInfo config_interface = {
|
||||
-1, "cockpit.Config", config_methods, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
void
|
||||
cockpit_dbus_config_startup (void)
|
||||
{
|
||||
GDBusConnection *connection;
|
||||
GError *error = NULL;
|
||||
|
||||
connection = cockpit_dbus_internal_server ();
|
||||
g_return_if_fail (connection != NULL);
|
||||
|
||||
g_dbus_connection_register_object (connection, "/config", &config_interface,
|
||||
&config_vtable, NULL, NULL, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_critical ("couldn't register DBus cockpit.Config object: %s", error->message);
|
||||
g_error_free (error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_object_unref (connection);
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/cockpitsocket.h"
|
||||
#include "cockpitdbusinternal.h"
|
||||
|
||||
static GDBusConnection *the_server = NULL;
|
||||
static GDBusConnection *the_client = NULL;
|
||||
const gchar *the_name = NULL;
|
||||
|
||||
GDBusConnection *
|
||||
cockpit_dbus_internal_client (void)
|
||||
{
|
||||
g_return_val_if_fail (the_client != NULL, NULL);
|
||||
return g_object_ref (the_client);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
cockpit_dbus_internal_name (void)
|
||||
{
|
||||
return the_name;
|
||||
}
|
||||
|
||||
GDBusConnection *
|
||||
cockpit_dbus_internal_server (void)
|
||||
{
|
||||
g_return_val_if_fail (the_server != NULL, NULL);
|
||||
return g_object_ref (the_server);
|
||||
}
|
||||
|
||||
static void
|
||||
on_complete_get_result (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GAsyncResult **ret = user_data;
|
||||
g_assert (*ret == NULL);
|
||||
*ret = g_object_ref (result);
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_dbus_internal_startup (gboolean interact)
|
||||
{
|
||||
GAsyncResult *rclient = NULL;
|
||||
GAsyncResult *rserver = NULL;
|
||||
GError *error = NULL;
|
||||
gchar *guid;
|
||||
|
||||
/*
|
||||
* When in interactive mode, we allow poking and prodding our internal
|
||||
* DBus interface. Therefore be on the session bus instead of peer-to-peer.
|
||||
*/
|
||||
if (interact)
|
||||
{
|
||||
the_server = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
||||
if (the_server)
|
||||
{
|
||||
the_name = g_dbus_connection_get_unique_name (the_server);
|
||||
the_client = g_object_ref (the_server);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_message ("couldn't connect to session bus: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
}
|
||||
|
||||
g_autoptr(GIOStream) one, two;
|
||||
cockpit_socket_streampair (&one, &two);
|
||||
|
||||
guid = g_dbus_generate_guid ();
|
||||
g_dbus_connection_new (one, guid,
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER |
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS,
|
||||
NULL, NULL, on_complete_get_result, &rserver);
|
||||
|
||||
g_dbus_connection_new (two, NULL, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
|
||||
NULL, NULL, on_complete_get_result, &rclient);
|
||||
|
||||
while (!rserver || !rclient)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
the_server = g_dbus_connection_new_finish (rserver, &error);
|
||||
if (the_server == NULL)
|
||||
{
|
||||
g_warning ("couldn't create internal connection: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
the_client = g_dbus_connection_new_finish (rclient, &error);
|
||||
if (the_client == NULL)
|
||||
{
|
||||
g_warning ("couldn't create internal connection: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_object_unref (rclient);
|
||||
g_object_unref (rserver);
|
||||
g_free (guid);
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_dbus_internal_cleanup (void)
|
||||
{
|
||||
g_clear_object (&the_client);
|
||||
g_clear_object (&the_server);
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __COCKPIT_DBUS_INTERNAL_H
|
||||
#define __COCKPIT_DBUS_INTERNAL_H
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <pwd.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GDBusConnection * cockpit_dbus_internal_client (void);
|
||||
|
||||
GDBusConnection * cockpit_dbus_internal_server (void);
|
||||
|
||||
const gchar * cockpit_dbus_internal_name (void);
|
||||
|
||||
void cockpit_dbus_internal_startup (gboolean interact);
|
||||
|
||||
void cockpit_dbus_internal_cleanup (void);
|
||||
|
||||
void cockpit_dbus_user_startup (struct passwd *pwd);
|
||||
|
||||
void cockpit_dbus_process_startup (void);
|
||||
|
||||
void cockpit_dbus_machines_startup (void);
|
||||
|
||||
void cockpit_dbus_machines_cleanup (void);
|
||||
|
||||
void cockpit_dbus_config_startup (void);
|
||||
|
||||
void cockpit_dbus_login_messages_startup (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __COCKPIT_DBUS_INTERNAL_H */
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_DBUS_JSON_H__
|
||||
#define COCKPIT_DBUS_JSON_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_DBUS_JSON (cockpit_dbus_json_get_type ())
|
||||
|
||||
GType cockpit_dbus_json_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitChannel * cockpit_dbus_json_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *dbus_service);
|
||||
|
||||
#endif /* COCKPIT_DBUS_JSON_H__ */
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2020 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/cockpitmemfdread.h"
|
||||
|
||||
#include "cockpitdbusinternal.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define INTROSPECTION_BLOB \
|
||||
"<node>" \
|
||||
"<interface name='cockpit.LoginMessages'>" \
|
||||
"<method name='Get'>" \
|
||||
"<arg name='messages' type='s' direction='out'/>" \
|
||||
"</method>" \
|
||||
"<method name='Dismiss'/>" \
|
||||
"</interface>" \
|
||||
"</node>"
|
||||
|
||||
static gchar *login_messages;
|
||||
|
||||
static void
|
||||
login_messages_method_call (GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *method_name,
|
||||
GVariant *parameters,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gpointer user_data)
|
||||
{
|
||||
if (g_str_equal (method_name, "Get"))
|
||||
{
|
||||
g_dbus_method_invocation_return_value (invocation,
|
||||
g_variant_new ("(s)", login_messages ?: "{}"));
|
||||
}
|
||||
else if (g_str_equal (method_name, "Dismiss"))
|
||||
{
|
||||
g_free (login_messages);
|
||||
login_messages = NULL;
|
||||
|
||||
g_dbus_method_invocation_return_value (invocation, NULL);
|
||||
}
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_dbus_login_messages_startup (void)
|
||||
{
|
||||
static const GDBusInterfaceVTable vtable = {
|
||||
.method_call = login_messages_method_call
|
||||
};
|
||||
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
/* If we fail to read the messages, log the failure, but otherwise
|
||||
* continue to register the service. We'll return '{}' in that case.
|
||||
*/
|
||||
if (!cockpit_memfd_read_from_envvar (&login_messages, "COCKPIT_LOGIN_MESSAGES_MEMFD", &error))
|
||||
{
|
||||
g_warning ("Unable to read login messages data: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_autoptr(GDBusConnection) connection = cockpit_dbus_internal_server ();
|
||||
g_return_if_fail (connection != NULL);
|
||||
|
||||
g_autoptr(GDBusNodeInfo) node = g_dbus_node_info_new_for_xml (INTROSPECTION_BLOB, &error);
|
||||
g_assert_no_error (error);
|
||||
GDBusInterfaceInfo *iface = g_dbus_node_info_lookup_interface (node, "cockpit.LoginMessages");
|
||||
g_assert (iface != NULL);
|
||||
|
||||
g_dbus_connection_register_object (connection, "/LoginMessages", iface, &vtable, NULL, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
}
|
||||
|
|
@ -1,253 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2017 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusinternal.h"
|
||||
#include "common/cockpitmachinesjson.h"
|
||||
#include "common/cockpitsystem.h"
|
||||
|
||||
#define MACHINES_SIG "a{sa{sv}}"
|
||||
|
||||
/* counts the number of file change events that have not yet gotten a PropertiesChanged signal */
|
||||
static guint pending_updates;
|
||||
|
||||
GFileMonitor *machines_monitor;
|
||||
|
||||
/* returns a floating GVariant */
|
||||
static GVariant *
|
||||
get_machines (void)
|
||||
{
|
||||
JsonNode *machines;
|
||||
GError *error = NULL;
|
||||
GVariant *variant;
|
||||
|
||||
machines = read_machines_json ();
|
||||
variant = json_gvariant_deserialize (machines, MACHINES_SIG, &error);
|
||||
/* if the signature does not match, we screwed up in the parser already */
|
||||
g_assert (variant != NULL);
|
||||
json_node_free (machines);
|
||||
return variant;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
machines_get_property (GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *property_name,
|
||||
GError **error,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_return_val_if_fail (property_name != NULL, NULL);
|
||||
|
||||
if (g_str_equal (property_name, "Machines"))
|
||||
return get_machines ();
|
||||
else
|
||||
g_return_val_if_reached (NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
machines_method_call (GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *method_name,
|
||||
GVariant *parameters,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gpointer user_data)
|
||||
{
|
||||
if (g_str_equal (method_name, "Update"))
|
||||
{
|
||||
const gchar *filename, *hostname;
|
||||
GVariant * info_v;
|
||||
JsonNode *info_json = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_variant_get (parameters, "(&s&s@a{sv})", &filename, &hostname, &info_v);
|
||||
info_json = json_gvariant_serialize (info_v);
|
||||
g_debug ("Updating %s for machine %s", filename, hostname);
|
||||
g_variant_unref (info_v);
|
||||
|
||||
if (update_machines_json (filename, hostname, info_json, &error))
|
||||
g_dbus_method_invocation_return_value (invocation, NULL);
|
||||
else
|
||||
g_dbus_method_invocation_take_error (invocation, error);
|
||||
json_node_free (info_json);
|
||||
}
|
||||
else
|
||||
g_return_if_reached ();
|
||||
}
|
||||
|
||||
/**
|
||||
* notify_properties:
|
||||
* @user_data: GDBusConnection to which updates get sent
|
||||
*
|
||||
* Send a PropertiesChanged signal for invalidating the Machines property. This
|
||||
* avoids parsing files and constructing the property when nobody is listening.
|
||||
* This gets called in reaction to changed *.json configuration files.
|
||||
*/
|
||||
static gboolean
|
||||
notify_properties (gpointer user_data)
|
||||
{
|
||||
GDBusConnection *connection = user_data;
|
||||
GVariant *signal_value;
|
||||
GVariantBuilder builder;
|
||||
GError *error = NULL;
|
||||
|
||||
/* reset pending counter before we do any actual work, to avoid races */
|
||||
pending_updates = 0;
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
|
||||
g_variant_builder_add (&builder, "s", "Machines");
|
||||
signal_value = g_variant_ref_sink (g_variant_new ("(sa{sv}as)", "cockpit.Machines", NULL, &builder));
|
||||
|
||||
g_dbus_connection_emit_signal (connection,
|
||||
NULL,
|
||||
"/machines",
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
signal_value,
|
||||
&error);
|
||||
if (error != NULL)
|
||||
{
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED))
|
||||
g_critical ("failed to send PropertiesChanged signal: %s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
g_variant_unref (signal_value);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_machines_changed (GFileMonitor *monitor,
|
||||
GFile *file,
|
||||
GFile *other_file,
|
||||
GFileMonitorEvent event_type,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar *path;
|
||||
|
||||
/* ignore uninteresting events; note that DELETED does not get a followup CHANGES_DONE_HINT */
|
||||
if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT && event_type != G_FILE_MONITOR_EVENT_DELETED)
|
||||
return;
|
||||
|
||||
path = g_file_get_path (file);
|
||||
if (g_str_has_suffix (path, ".json"))
|
||||
{
|
||||
g_debug ("on_machines_changed: event type %i on %s", event_type, path);
|
||||
/* change events tend to come in batches, so slightly delay the re-reading of
|
||||
* files and sending PropertiesChanged; if we already have queued up an
|
||||
* update, don't queue it again */
|
||||
if (pending_updates++ == 0)
|
||||
g_timeout_add (100, notify_properties, user_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_debug ("on_machines_changed: ignoring event type %i on non-.json file %s", event_type, path);
|
||||
}
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
static GDBusInterfaceVTable machines_vtable = {
|
||||
.method_call = machines_method_call,
|
||||
.get_property = machines_get_property,
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo machines_property = {
|
||||
-1, "Machines", MACHINES_SIG, G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo *machines_properties[] = {
|
||||
&machines_property,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo machines_update_filename_arg = {
|
||||
-1, "filename", "s", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo machines_update_hostname_arg = {
|
||||
-1, "hostname", "s", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo machines_update_info_arg = {
|
||||
-1, "info", "a{sv}", NULL
|
||||
};
|
||||
|
||||
static GDBusArgInfo *machines_update_args[] = {
|
||||
&machines_update_filename_arg,
|
||||
&machines_update_hostname_arg,
|
||||
&machines_update_info_arg,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusMethodInfo machines_update_method = {
|
||||
-1, "Update", machines_update_args, NULL, NULL
|
||||
};
|
||||
|
||||
static GDBusMethodInfo *machines_methods[] = {
|
||||
&machines_update_method,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusInterfaceInfo machines_interface = {
|
||||
-1, "cockpit.Machines", machines_methods, NULL, machines_properties, NULL
|
||||
};
|
||||
|
||||
void
|
||||
cockpit_dbus_machines_startup (void)
|
||||
{
|
||||
GDBusConnection *connection;
|
||||
GFile *machines_monitor_file;
|
||||
GError *error = NULL;
|
||||
|
||||
connection = cockpit_dbus_internal_server ();
|
||||
g_return_if_fail (connection != NULL);
|
||||
|
||||
g_dbus_connection_register_object (connection, "/machines", &machines_interface,
|
||||
&machines_vtable, NULL, NULL, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_critical ("couldn't register DBus cockpit.Machines object: %s", error->message);
|
||||
g_error_free (error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* watch for file changes and send D-Bus signal for it */
|
||||
machines_monitor_file = g_file_new_for_path (get_machines_json_dir ());
|
||||
machines_monitor = g_file_monitor (machines_monitor_file, G_FILE_MONITOR_NONE, NULL, &error);
|
||||
g_object_unref (machines_monitor_file);
|
||||
if (machines_monitor == NULL)
|
||||
{
|
||||
g_critical ("couldn't set up file watch: %s", error->message);
|
||||
g_error_free (error);
|
||||
return;
|
||||
}
|
||||
g_signal_connect (machines_monitor, "changed", G_CALLBACK (on_machines_changed), connection);
|
||||
|
||||
g_object_unref (connection);
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_dbus_machines_cleanup (void)
|
||||
{
|
||||
g_object_unref (machines_monitor);
|
||||
}
|
||||
|
|
@ -1,483 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusmeta.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static JsonArray *
|
||||
build_meta_arguments (GDBusArgInfo **args)
|
||||
{
|
||||
JsonArray *arguments = json_array_new ();
|
||||
while (*args)
|
||||
{
|
||||
json_array_add_string_element (arguments, (*args)->signature);
|
||||
args++;
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
build_meta_method (GDBusMethodInfo *meth)
|
||||
{
|
||||
JsonObject *method = json_object_new ();
|
||||
if (meth->in_args)
|
||||
{
|
||||
json_object_set_array_member (method, "in",
|
||||
build_meta_arguments (meth->in_args));
|
||||
}
|
||||
if (meth->out_args)
|
||||
{
|
||||
json_object_set_array_member (method, "out",
|
||||
build_meta_arguments (meth->out_args));
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
build_meta_signal (GDBusSignalInfo *sig)
|
||||
{
|
||||
JsonObject *signal = json_object_new ();
|
||||
if (sig->args)
|
||||
{
|
||||
json_object_set_array_member (signal, "in",
|
||||
build_meta_arguments (sig->args));
|
||||
}
|
||||
return signal;
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
build_meta_property (GDBusPropertyInfo *prop)
|
||||
{
|
||||
JsonObject *property = json_object_new ();;
|
||||
GString *flags = g_string_new ("");
|
||||
|
||||
if (prop->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)
|
||||
g_string_append_c (flags, 'r');
|
||||
if (prop->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)
|
||||
g_string_append_c (flags, 'w');
|
||||
json_object_set_string_member (property, "flags", flags->str);
|
||||
if (prop->signature)
|
||||
json_object_set_string_member (property, "type", prop->signature);
|
||||
g_string_free (flags, TRUE);
|
||||
return property;
|
||||
}
|
||||
|
||||
JsonObject *
|
||||
cockpit_dbus_meta_build (GDBusInterfaceInfo *iface)
|
||||
{
|
||||
JsonObject *interface;
|
||||
JsonObject *methods;
|
||||
JsonObject *properties;
|
||||
JsonObject *signals;
|
||||
guint i;
|
||||
|
||||
g_return_val_if_fail (iface != NULL, NULL);
|
||||
|
||||
interface = json_object_new ();
|
||||
|
||||
if (iface->methods)
|
||||
{
|
||||
methods = json_object_new ();
|
||||
for (i = 0; iface->methods[i] != NULL; i++)
|
||||
{
|
||||
json_object_set_object_member (methods, iface->methods[i]->name,
|
||||
build_meta_method (iface->methods[i]));
|
||||
}
|
||||
json_object_set_object_member (interface, "methods", methods);
|
||||
}
|
||||
|
||||
if (iface->properties)
|
||||
{
|
||||
properties = json_object_new ();
|
||||
for (i = 0; iface->properties[i] != NULL; i++)
|
||||
{
|
||||
json_object_set_object_member (properties, iface->properties[i]->name,
|
||||
build_meta_property (iface->properties[i]));
|
||||
}
|
||||
json_object_set_object_member (interface, "properties", properties);
|
||||
}
|
||||
|
||||
if (iface->signals)
|
||||
{
|
||||
signals = json_object_new ();
|
||||
for (i = 0; iface->signals[i] != NULL; i++)
|
||||
{
|
||||
json_object_set_object_member (signals, iface->signals[i]->name,
|
||||
build_meta_signal (iface->signals[i]));
|
||||
}
|
||||
json_object_set_object_member (interface, "signals", signals);
|
||||
}
|
||||
|
||||
return interface;
|
||||
}
|
||||
|
||||
static GDBusArgInfo **
|
||||
parse_meta_arguments (JsonArray *arguments,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *signature;
|
||||
GDBusArgInfo *arg;
|
||||
GPtrArray *args;
|
||||
guint i, length;
|
||||
JsonNode *node;
|
||||
|
||||
args = g_ptr_array_new ();
|
||||
g_ptr_array_set_free_func (args, (GDestroyNotify)g_dbus_arg_info_unref);
|
||||
|
||||
length = json_array_get_length (arguments);
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
node = json_array_get_element (arguments, i);
|
||||
if (!JSON_NODE_HOLDS_VALUE(node) || json_node_get_value_type (node) != G_TYPE_STRING)
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid argument in dbus meta field");
|
||||
break;
|
||||
}
|
||||
|
||||
signature = json_node_get_string (node);
|
||||
if (!g_variant_is_signature (signature))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"argument in dbus meta field has invalid signature: %s", signature);
|
||||
break;
|
||||
}
|
||||
|
||||
arg = g_new0 (GDBusArgInfo, 1);
|
||||
arg->ref_count = 1;
|
||||
arg->name = g_strdup_printf ("argument_%u", i);
|
||||
arg->signature = g_strdup (signature);
|
||||
g_ptr_array_add (args, arg);
|
||||
}
|
||||
|
||||
if (i != length)
|
||||
{
|
||||
g_ptr_array_free (args, TRUE);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_ptr_array_add (args, NULL);
|
||||
return (GDBusArgInfo **)g_ptr_array_free (args, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
static GDBusMethodInfo *
|
||||
parse_meta_method (const gchar *method_name,
|
||||
JsonObject *method,
|
||||
GError **error)
|
||||
{
|
||||
GDBusMethodInfo *ret = NULL;
|
||||
GDBusMethodInfo *meth;
|
||||
JsonArray *args;
|
||||
|
||||
meth = g_new0 (GDBusMethodInfo, 1);
|
||||
meth->ref_count = 1;
|
||||
meth->name = g_strdup (method_name);
|
||||
|
||||
if (!cockpit_json_get_array (method, "in", NULL, &args))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid \"in\" field in dbus meta method: %s", method_name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (args && json_array_get_length (args) > 0)
|
||||
{
|
||||
meth->in_args = parse_meta_arguments (args, error);
|
||||
if (!meth->in_args)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_array (method, "out", NULL, &args))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid \"out\" field in dbus meta method: %s", method_name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (args && json_array_get_length (args) > 0)
|
||||
{
|
||||
meth->out_args = parse_meta_arguments (args, error);
|
||||
if (!meth->out_args)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = meth;
|
||||
meth = NULL;
|
||||
|
||||
out:
|
||||
if (meth)
|
||||
g_dbus_method_info_unref (meth);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GDBusSignalInfo *
|
||||
parse_meta_signal (const gchar *signal_name,
|
||||
JsonObject *signal,
|
||||
GError **error)
|
||||
{
|
||||
GDBusSignalInfo *ret = NULL;
|
||||
GDBusSignalInfo *sig;
|
||||
JsonArray *args;
|
||||
|
||||
sig = g_new0 (GDBusSignalInfo, 1);
|
||||
sig->ref_count = 1;
|
||||
sig->name = g_strdup (signal_name);
|
||||
|
||||
if (!cockpit_json_get_array (signal, "in", NULL, &args))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid \"in\" field in dbus meta signal: %s", signal_name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (args && json_array_get_length (args) > 0)
|
||||
{
|
||||
sig->args = parse_meta_arguments (args, error);
|
||||
if (!sig->args)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = sig;
|
||||
sig = NULL;
|
||||
|
||||
out:
|
||||
if (sig)
|
||||
g_dbus_signal_info_unref (sig);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GDBusPropertyInfo *
|
||||
parse_meta_property (const gchar *property_name,
|
||||
JsonObject *property,
|
||||
GError **error)
|
||||
{
|
||||
GDBusPropertyInfo *prop;
|
||||
GDBusPropertyInfo *ret = NULL;
|
||||
const gchar *flags;
|
||||
const gchar *type;
|
||||
|
||||
prop = g_new0 (GDBusPropertyInfo, 1);
|
||||
prop->ref_count = 1;
|
||||
prop->name = g_strdup (property_name);
|
||||
|
||||
if (!cockpit_json_get_string (property, "flags", NULL, &flags))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid \"flags\" field in dbus property: %s", property_name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (flags && strchr (flags, 'r'))
|
||||
prop->flags |= G_DBUS_PROPERTY_INFO_FLAGS_READABLE;
|
||||
if (flags && strchr (flags, 'w'))
|
||||
prop->flags |= G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE;
|
||||
|
||||
if (!cockpit_json_get_string (property, "type", NULL, &type))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid \"type\" field in dbus property: %s", property_name);
|
||||
goto out;
|
||||
}
|
||||
else if (!type)
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"missing \"type\" field in dbus property: %s", property_name);
|
||||
goto out;
|
||||
}
|
||||
else if (!g_variant_is_signature (type))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"the \"type\" field in dbus property is not a dbus signature: %s", type);
|
||||
goto out;
|
||||
}
|
||||
|
||||
prop->signature = g_strdup (type);
|
||||
|
||||
ret = prop;
|
||||
prop = NULL;
|
||||
|
||||
out:
|
||||
if (prop)
|
||||
g_dbus_property_info_unref (prop);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GDBusInterfaceInfo *
|
||||
cockpit_dbus_meta_parse (const gchar *iface_name,
|
||||
JsonObject *interface,
|
||||
GError **error)
|
||||
{
|
||||
GDBusInterfaceInfo *ret = NULL;
|
||||
GDBusInterfaceInfo *iface;
|
||||
GDBusMethodInfo *meth;
|
||||
GDBusSignalInfo *sig;
|
||||
GDBusPropertyInfo *prop;
|
||||
JsonObject *methods;
|
||||
JsonObject *method;
|
||||
JsonObject *signals;
|
||||
JsonObject *signal;
|
||||
JsonObject *properties;
|
||||
JsonObject *property;
|
||||
GPtrArray *array = NULL;
|
||||
GList *names = NULL, *l;
|
||||
|
||||
g_return_val_if_fail (iface_name != NULL, NULL);
|
||||
g_return_val_if_fail (interface != NULL, NULL);
|
||||
g_return_val_if_fail (!error || !*error, NULL);
|
||||
|
||||
iface = g_new0 (GDBusInterfaceInfo, 1);
|
||||
iface->name = g_strdup (iface_name);
|
||||
iface->ref_count = 1;
|
||||
|
||||
if (!cockpit_json_get_object (interface, "methods", NULL, &methods))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid \"methods\" field in dbus meta structure");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (methods)
|
||||
{
|
||||
array = g_ptr_array_new ();
|
||||
g_ptr_array_set_free_func (array, (GDestroyNotify)g_dbus_method_info_unref);
|
||||
|
||||
names = json_object_get_members (methods);
|
||||
for (l = names; l != NULL; l = g_list_next (l))
|
||||
{
|
||||
if (!cockpit_json_get_object (methods, l->data, NULL, &method))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid method field in dbus meta structure: %s",
|
||||
(const gchar *)l->data);
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_assert (method != NULL);
|
||||
meth = parse_meta_method (l->data, method, error);
|
||||
if (!meth)
|
||||
goto out;
|
||||
|
||||
g_ptr_array_add (array, meth);
|
||||
}
|
||||
|
||||
g_list_free (names);
|
||||
names = NULL;
|
||||
|
||||
g_ptr_array_add (array, NULL);
|
||||
iface->methods = (GDBusMethodInfo **)g_ptr_array_free (array, FALSE);
|
||||
array = NULL;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_object (interface, "signals", NULL, &signals))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid \"signals\" field in dbus meta structure");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (signals)
|
||||
{
|
||||
array = g_ptr_array_new ();
|
||||
g_ptr_array_set_free_func (array, (GDestroyNotify)g_dbus_signal_info_unref);
|
||||
|
||||
names = json_object_get_members (signals);
|
||||
for (l = names; l != NULL; l = g_list_next (l))
|
||||
{
|
||||
if (!cockpit_json_get_object (signals, l->data, NULL, &signal))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid signal field in dbus meta structure: %s",
|
||||
(const gchar *)l->data);
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_assert (signal != NULL);
|
||||
sig = parse_meta_signal (l->data, signal, error);
|
||||
if (!sig)
|
||||
goto out;
|
||||
|
||||
g_ptr_array_add (array, sig);
|
||||
}
|
||||
|
||||
g_list_free (names);
|
||||
names = NULL;
|
||||
|
||||
g_ptr_array_add (array, NULL);
|
||||
iface->signals = (GDBusSignalInfo **)g_ptr_array_free (array, FALSE);
|
||||
array = NULL;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_object (interface, "properties", NULL, &properties))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid \"properties\" field in dbus meta structure");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (properties)
|
||||
{
|
||||
array = g_ptr_array_new ();
|
||||
g_ptr_array_set_free_func (array, (GDestroyNotify)g_dbus_property_info_unref);
|
||||
|
||||
names = json_object_get_members (properties);
|
||||
for (l = names; l != NULL; l = g_list_next (l))
|
||||
{
|
||||
if (!cockpit_json_get_object (properties, l->data, NULL, &property))
|
||||
{
|
||||
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"invalid property field in dbus meta structure: %s",
|
||||
(const gchar *)l->data);
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_assert (property != NULL);
|
||||
prop = parse_meta_property (l->data, property, error);
|
||||
if (!prop)
|
||||
goto out;
|
||||
|
||||
g_ptr_array_add (array, prop);
|
||||
}
|
||||
|
||||
g_list_free (names);
|
||||
names = NULL;
|
||||
|
||||
g_ptr_array_add (array, NULL);
|
||||
iface->properties = (GDBusPropertyInfo **)g_ptr_array_free (array, FALSE);
|
||||
array = NULL;
|
||||
}
|
||||
|
||||
ret = iface;
|
||||
iface = NULL;
|
||||
|
||||
out:
|
||||
if (iface)
|
||||
g_dbus_interface_info_unref (iface);
|
||||
if (array)
|
||||
g_ptr_array_free (array, TRUE);
|
||||
if (names)
|
||||
g_list_free (names);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_DBUS_META_H__
|
||||
#define COCKPIT_DBUS_META_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
JsonObject * cockpit_dbus_meta_build (GDBusInterfaceInfo *iface);
|
||||
|
||||
GDBusInterfaceInfo * cockpit_dbus_meta_parse (const gchar *iface_name,
|
||||
JsonObject *interface,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_DBUS_META_H__ */
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusinternal.h"
|
||||
|
||||
#include "common/cockpitsystem.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static GVariant *
|
||||
build_environment (void)
|
||||
{
|
||||
GVariantBuilder builder;
|
||||
GVariant *variant;
|
||||
|
||||
gchar **environ = g_listenv ();
|
||||
gint i;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
|
||||
for (i = 0; environ[i] != NULL; i++)
|
||||
{
|
||||
const gchar *value = g_getenv (environ[i]);
|
||||
if (value)
|
||||
g_variant_builder_add (&builder, "{ss}", environ[i], value);
|
||||
}
|
||||
|
||||
g_strfreev (environ);
|
||||
variant = g_variant_builder_end (&builder);
|
||||
return variant;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
lookup_session_id (void)
|
||||
{
|
||||
char *session_id;
|
||||
GVariant *variant;
|
||||
|
||||
session_id = cockpit_system_session_id ();
|
||||
variant = g_variant_new_string (session_id ? session_id : "");
|
||||
free (session_id);
|
||||
return variant;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
process_get_property (GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *property_name,
|
||||
GError **error,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_return_val_if_fail (property_name != NULL, NULL);
|
||||
|
||||
if (g_str_equal (property_name, "Pid"))
|
||||
return g_variant_new_uint32 (getpid ());
|
||||
else if (g_str_equal (property_name, "Uid"))
|
||||
return g_variant_new_int32 (getuid ());
|
||||
else if (g_str_equal (property_name, "SessionId"))
|
||||
return lookup_session_id ();
|
||||
else if (g_str_equal (property_name, "StartTime"))
|
||||
return g_variant_new_uint64 (cockpit_system_process_start_time ());
|
||||
else if (g_str_equal (property_name, "Environment") || g_str_equal (property_name, "Variables"))
|
||||
return build_environment ();
|
||||
else
|
||||
g_return_val_if_reached (NULL);
|
||||
}
|
||||
|
||||
static GDBusInterfaceVTable process_vtable = {
|
||||
.get_property = process_get_property,
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo pid_property = {
|
||||
-1, "Pid", "u", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo uid_property = {
|
||||
-1, "Uid", "i", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo start_time_property = {
|
||||
-1, "StartTime", "t", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo session_id_property = {
|
||||
-1, "SessionId", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL,
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo environment_property = {
|
||||
-1, "Environment", "a{ss}", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo *process_properties[] = {
|
||||
&pid_property,
|
||||
&uid_property,
|
||||
&start_time_property,
|
||||
&session_id_property,
|
||||
&environment_property,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusInterfaceInfo process_interface = {
|
||||
-1, "cockpit.Process", NULL, NULL, process_properties, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo variables_property = {
|
||||
-1, "Variables", "a{ss}", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo *environment_properties[] = {
|
||||
&variables_property,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusInterfaceInfo environment_interface = {
|
||||
-1, "cockpit.Environment", NULL, NULL, environment_properties, NULL
|
||||
};
|
||||
|
||||
void
|
||||
cockpit_dbus_process_startup (void)
|
||||
{
|
||||
GDBusConnection *connection;
|
||||
GError *error = NULL;
|
||||
|
||||
connection = cockpit_dbus_internal_server ();
|
||||
g_return_if_fail (connection != NULL);
|
||||
|
||||
g_dbus_connection_register_object (connection, "/environment", &environment_interface,
|
||||
&process_vtable, NULL, NULL, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_critical ("couldn't register DBus cockpit.Environment object: %s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_dbus_connection_register_object (connection, "/bridge", &process_interface,
|
||||
&process_vtable, NULL, NULL, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_critical ("couldn't register DBus cockpit.Process object: %s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_object_unref (connection);
|
||||
}
|
||||
|
|
@ -1,341 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusrules.h"
|
||||
|
||||
#include "cockpitpaths.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* These are rules similar to what a dbus-daemon would use when processing
|
||||
* AddMatch based forwarding. These are used to identify which signals a
|
||||
* client wanted to subscribe to, and which paths/interfaces a client
|
||||
* wanted to watch.
|
||||
*
|
||||
* We use several speed ups to quickly identify paths that are not matched
|
||||
* by the rules ... before then going into matching each rule in turn.
|
||||
*
|
||||
* An empty rule set forwards nothing. It has a fast bypass flag which
|
||||
* disables all the logic.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
gint refs;
|
||||
gchar *path;
|
||||
gboolean is_namespace;
|
||||
gchar *interface;
|
||||
gchar *member;
|
||||
gchar *arg0;
|
||||
} RuleData;
|
||||
|
||||
static guint32
|
||||
rule_hash (gconstpointer data)
|
||||
{
|
||||
const RuleData *rule = data;
|
||||
return g_str_hash (rule->path) ^
|
||||
g_int_hash (&rule->is_namespace) ^
|
||||
(rule->interface ? g_str_hash (rule->interface) : 0) ^
|
||||
(rule->member ? g_str_hash (rule->member) : 0) ^
|
||||
(rule->arg0 ? g_str_hash (rule->arg0) : 0);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
rule_equal (gconstpointer one,
|
||||
gconstpointer two)
|
||||
{
|
||||
const RuleData *r1 = one;
|
||||
const RuleData *r2 = two;
|
||||
return r1->is_namespace == r2->is_namespace &&
|
||||
g_str_equal (r1->path, r2->path) &&
|
||||
g_strcmp0 (r1->interface, r2->interface) == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
rule_dump (RuleData *rule,
|
||||
GString *string)
|
||||
{
|
||||
g_string_append (string, "{ ");
|
||||
if (rule->path)
|
||||
g_string_append_printf (string, "%s: \"%s\", ", rule->is_namespace ? "path_namespace" : "path", rule->path);
|
||||
if (rule->interface)
|
||||
g_string_append_printf (string, "interface: \"%s\", ", rule->interface);
|
||||
if (rule->arg0)
|
||||
g_string_append_printf (string, "arg0: \"%s\", ", rule->arg0);
|
||||
if (rule->member)
|
||||
g_string_append_printf (string, "member: \"%s\", ", rule->member);
|
||||
g_string_append (string, "}");
|
||||
}
|
||||
|
||||
static void
|
||||
rule_free (gpointer data)
|
||||
{
|
||||
RuleData *rule = data;
|
||||
g_free (rule->path);
|
||||
g_free (rule->interface);
|
||||
g_free (rule->arg0);
|
||||
g_free (rule->member);
|
||||
g_slice_free (RuleData, rule);
|
||||
}
|
||||
|
||||
struct _CockpitDBusRules {
|
||||
GHashTable *all;
|
||||
GHashTable *paths;
|
||||
GTree *path_namespaces;
|
||||
gboolean all_paths;
|
||||
gboolean only_paths;
|
||||
gboolean nothing;
|
||||
};
|
||||
|
||||
gchar *
|
||||
cockpit_dbus_rules_to_string (CockpitDBusRules *rules)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
GString *string;
|
||||
RuleData *rule;
|
||||
|
||||
string = g_string_new ("[ ");
|
||||
g_hash_table_iter_init (&iter, rules->all);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&rule, NULL))
|
||||
{
|
||||
rule_dump (rule, string);
|
||||
g_string_append (string, ", ");
|
||||
}
|
||||
g_string_append (string, "]");
|
||||
|
||||
return g_string_free (string, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
rule_match (RuleData *rule,
|
||||
const gchar *path,
|
||||
const gchar *interface,
|
||||
const gchar *member,
|
||||
const gchar *arg0)
|
||||
{
|
||||
if (!g_str_equal (rule->path, path))
|
||||
{
|
||||
if (!rule->is_namespace || !cockpit_path_equal_or_ancestor (path, rule->path))
|
||||
return FALSE;
|
||||
}
|
||||
if (interface && rule->interface && strcmp (interface, rule->interface) != 0)
|
||||
return FALSE;
|
||||
if (member && rule->member && strcmp (member, rule->member) != 0)
|
||||
return FALSE;
|
||||
|
||||
/* Note that if arg0 came in as NULL, it means message doesn't have arg0: no match */
|
||||
if (rule->arg0 && g_strcmp0 (arg0, rule->arg0) != 0)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_dbus_rules_match (CockpitDBusRules *rules,
|
||||
const gchar *path,
|
||||
const gchar *interface,
|
||||
const gchar *member,
|
||||
const gchar *arg0)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
RuleData *rule;
|
||||
|
||||
g_return_val_if_fail (path != NULL, FALSE);
|
||||
|
||||
if (rules->nothing)
|
||||
return FALSE;
|
||||
|
||||
if (!rules->all_paths)
|
||||
{
|
||||
if (!cockpit_paths_contain_or_ancestor (rules->path_namespaces, path) &&
|
||||
!g_hash_table_lookup (rules->paths, path))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (rules->only_paths)
|
||||
return TRUE;
|
||||
|
||||
g_hash_table_iter_init (&iter, rules->all);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&rule, NULL))
|
||||
{
|
||||
if (rule_match (rule, path, interface, member, arg0))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
CockpitDBusRules *
|
||||
cockpit_dbus_rules_new (void)
|
||||
{
|
||||
CockpitDBusRules *rules = g_new0 (CockpitDBusRules, 1);
|
||||
rules->nothing = TRUE;
|
||||
return rules;
|
||||
}
|
||||
|
||||
static RuleData *
|
||||
rule_lookup (CockpitDBusRules *rules,
|
||||
const gchar *path,
|
||||
gboolean is_namespace,
|
||||
const gchar *interface,
|
||||
const gchar *member,
|
||||
const gchar *arg0)
|
||||
{
|
||||
RuleData key = {
|
||||
.refs = 0,
|
||||
.path = (gchar *)path,
|
||||
.is_namespace = is_namespace,
|
||||
.interface = (gchar *)interface,
|
||||
.member = (gchar *)member,
|
||||
.arg0 = (gchar *)arg0
|
||||
};
|
||||
|
||||
return g_hash_table_lookup (rules->all, &key);
|
||||
}
|
||||
|
||||
static void
|
||||
recompile_rules (CockpitDBusRules *rules)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
RuleData *rule;
|
||||
|
||||
if (rules->paths)
|
||||
g_hash_table_remove_all (rules->paths);
|
||||
else
|
||||
rules->paths = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
if (rules->path_namespaces)
|
||||
g_tree_destroy (rules->path_namespaces);
|
||||
rules->path_namespaces = cockpit_paths_new ();
|
||||
|
||||
rules->all_paths = FALSE;
|
||||
rules->nothing = TRUE;
|
||||
rules->only_paths = TRUE;
|
||||
|
||||
g_hash_table_iter_init (&iter, rules->all);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&rule, NULL))
|
||||
{
|
||||
rules->nothing = FALSE;
|
||||
|
||||
if (rule->is_namespace)
|
||||
{
|
||||
if (g_str_equal (rule->path, "/"))
|
||||
rules->all_paths = TRUE;
|
||||
cockpit_paths_add (rules->path_namespaces, rule->path);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_hash_table_add (rules->paths, rule->path);
|
||||
}
|
||||
|
||||
if (rule->interface || rule->member || rule->arg0)
|
||||
rules->only_paths = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_dbus_rules_add (CockpitDBusRules *rules,
|
||||
const gchar *path,
|
||||
gboolean is_namespace,
|
||||
const gchar *interface,
|
||||
const gchar *member,
|
||||
const gchar *arg0)
|
||||
{
|
||||
RuleData *rule = NULL;
|
||||
|
||||
if (!path)
|
||||
{
|
||||
path = "/";
|
||||
is_namespace = TRUE;
|
||||
}
|
||||
|
||||
if (!rules->nothing)
|
||||
rule = rule_lookup (rules, path, is_namespace, interface, member, arg0);
|
||||
|
||||
if (rules->all == NULL)
|
||||
rules->all = g_hash_table_new_full (rule_hash, rule_equal, rule_free, NULL);
|
||||
|
||||
if (rule == NULL)
|
||||
{
|
||||
rule = g_slice_new0 (RuleData);
|
||||
rule->refs = 1;
|
||||
rule->path = g_strdup (path);
|
||||
rule->is_namespace = is_namespace;
|
||||
rule->interface = g_strdup (interface);
|
||||
rule->member = g_strdup (member);
|
||||
rule->arg0 = g_strdup (arg0);
|
||||
g_hash_table_add (rules->all, rule);
|
||||
recompile_rules (rules);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
rule->refs++;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_dbus_rules_remove (CockpitDBusRules *rules,
|
||||
const gchar *path,
|
||||
gboolean is_namespace,
|
||||
const gchar *interface,
|
||||
const gchar *member,
|
||||
const gchar *arg0)
|
||||
{
|
||||
RuleData *rule = NULL;
|
||||
|
||||
if (!path)
|
||||
{
|
||||
path = "/";
|
||||
is_namespace = TRUE;
|
||||
}
|
||||
|
||||
if (rules->nothing)
|
||||
return FALSE;
|
||||
|
||||
rule = rule_lookup (rules, path, is_namespace, interface, member, arg0);
|
||||
if (rule == NULL)
|
||||
return FALSE;
|
||||
|
||||
rule->refs--;
|
||||
if (rule->refs == 0)
|
||||
{
|
||||
g_hash_table_remove (rules->all, rule);
|
||||
recompile_rules (rules);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_dbus_rules_free (CockpitDBusRules *rules)
|
||||
{
|
||||
if (rules->all)
|
||||
g_hash_table_destroy (rules->all);
|
||||
if (rules->paths)
|
||||
g_hash_table_destroy (rules->paths);
|
||||
if (rules->path_namespaces)
|
||||
g_tree_destroy (rules->path_namespaces);
|
||||
g_free (rules);
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_DBUS_RULES_H_
|
||||
#define COCKPIT_DBUS_RULES_H_
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
typedef struct _CockpitDBusRules CockpitDBusRules;
|
||||
|
||||
CockpitDBusRules * cockpit_dbus_rules_new (void);
|
||||
|
||||
gboolean cockpit_dbus_rules_match (CockpitDBusRules *rules,
|
||||
const gchar *path,
|
||||
const gchar *interface,
|
||||
const gchar *member,
|
||||
const gchar *arg0);
|
||||
|
||||
gboolean cockpit_dbus_rules_add (CockpitDBusRules *rules,
|
||||
const gchar *path,
|
||||
gboolean is_namespace,
|
||||
const gchar *interface,
|
||||
const gchar *member,
|
||||
const gchar *arg0);
|
||||
|
||||
gboolean cockpit_dbus_rules_remove (CockpitDBusRules *rules,
|
||||
const gchar *path,
|
||||
gboolean is_namespace,
|
||||
const gchar *interface,
|
||||
const gchar *member,
|
||||
const gchar *arg0);
|
||||
|
||||
gchar * cockpit_dbus_rules_to_string (CockpitDBusRules *rules);
|
||||
|
||||
void cockpit_dbus_rules_free (CockpitDBusRules *rules);
|
||||
|
||||
#endif /* COCKPIT_DBUS_RULES_H_ */
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusinternal.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <string.h>
|
||||
|
||||
static void
|
||||
variant_table_sink (GHashTable *props,
|
||||
const gchar *name,
|
||||
GVariant *value)
|
||||
{
|
||||
g_hash_table_insert (props, (gchar *)name, g_variant_ref_sink (value));
|
||||
}
|
||||
|
||||
static void
|
||||
populate_passwd_props (GHashTable *props,
|
||||
struct passwd *pw)
|
||||
{
|
||||
if (pw == NULL)
|
||||
{
|
||||
variant_table_sink (props, "Id", g_variant_new_int64 (geteuid ()));
|
||||
variant_table_sink (props, "Name", g_variant_new_string (""));
|
||||
variant_table_sink (props, "Full", g_variant_new_string (""));
|
||||
variant_table_sink (props, "Home", g_variant_new_string (""));
|
||||
variant_table_sink (props, "Shell", g_variant_new_string (""));
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t n;
|
||||
gchar *full;
|
||||
|
||||
n = strlen (pw->pw_gecos);
|
||||
while (n > 0 && pw->pw_gecos[n - 1] == ',')
|
||||
n--;
|
||||
full = g_strndup (pw->pw_gecos, n);
|
||||
|
||||
variant_table_sink (props, "Id", g_variant_new_int64 (pw->pw_uid));
|
||||
variant_table_sink (props, "Name", g_variant_new_string (pw->pw_name));
|
||||
variant_table_sink (props, "Full", g_variant_new_string (full));
|
||||
variant_table_sink (props, "Home", g_variant_new_string (pw->pw_dir));
|
||||
variant_table_sink (props, "Shell", g_variant_new_string (pw->pw_shell));
|
||||
|
||||
g_free (full);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
populate_group_prop (GHashTable *props)
|
||||
{
|
||||
struct group *gr;
|
||||
GPtrArray *array;
|
||||
gid_t *list = NULL;
|
||||
gid_t gid;
|
||||
int ret, i;
|
||||
|
||||
gid = getegid ();
|
||||
|
||||
ret = getgroups (0, NULL);
|
||||
if (ret > 0)
|
||||
{
|
||||
list = g_new (gid_t, ret);
|
||||
ret = getgroups (ret, list);
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
g_warning ("couldn't load list of groups: %s", g_strerror (errno));
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
array = g_ptr_array_new ();
|
||||
|
||||
gr = getgrgid (gid);
|
||||
if (gr == NULL)
|
||||
g_warning ("couldn't load group info for %d: %s", (int)gid, g_strerror (errno));
|
||||
else
|
||||
g_ptr_array_add (array, g_variant_new_string (gr->gr_name));
|
||||
|
||||
for (i = 0; i < ret; i++)
|
||||
{
|
||||
if (list[i] == gid)
|
||||
continue;
|
||||
|
||||
gr = getgrgid (list[i]);
|
||||
if (gr == NULL)
|
||||
{
|
||||
g_warning ("couldn't load group info for %d: %s", (int)gid, g_strerror (errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
g_ptr_array_add (array, g_variant_new_string (gr->gr_name));
|
||||
}
|
||||
|
||||
variant_table_sink (props, "Groups", g_variant_new_array (G_VARIANT_TYPE_STRING,
|
||||
(GVariant * const*)array->pdata,array->len));
|
||||
|
||||
g_ptr_array_free (array, TRUE);
|
||||
g_free (list);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
user_get_property (GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *property_name,
|
||||
GError **error,
|
||||
gpointer user_data)
|
||||
{
|
||||
GHashTable *props = user_data;
|
||||
|
||||
if (!g_hash_table_lookup (props, "Groups"))
|
||||
populate_group_prop (props);
|
||||
|
||||
GVariant *value = g_hash_table_lookup (props, property_name);
|
||||
g_return_val_if_fail (value != NULL, NULL);
|
||||
return g_variant_ref (value);
|
||||
}
|
||||
|
||||
static GDBusPropertyInfo user_name_property = {
|
||||
-1, "Name", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo user_full_property = {
|
||||
-1, "Full", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo user_id_property = {
|
||||
-1, "Id", "x", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo user_home_property = {
|
||||
-1, "Home", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo user_shell_property = {
|
||||
-1, "Shell", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo user_groups_property = {
|
||||
-1, "Groups", "as", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL
|
||||
};
|
||||
|
||||
static GDBusPropertyInfo *user_properties[] = {
|
||||
&user_name_property,
|
||||
&user_full_property,
|
||||
&user_id_property,
|
||||
&user_shell_property,
|
||||
&user_home_property,
|
||||
&user_groups_property,
|
||||
NULL
|
||||
};
|
||||
|
||||
static GDBusInterfaceInfo user_interface = {
|
||||
-1, "cockpit.User", NULL, NULL, user_properties, NULL
|
||||
};
|
||||
|
||||
static GDBusInterfaceVTable user_vtable = {
|
||||
.get_property = user_get_property,
|
||||
};
|
||||
|
||||
void
|
||||
cockpit_dbus_user_startup (struct passwd *pwd)
|
||||
{
|
||||
GDBusConnection *connection;
|
||||
GHashTable *props;
|
||||
GError *error = NULL;
|
||||
|
||||
connection = cockpit_dbus_internal_server ();
|
||||
g_return_if_fail (connection != NULL);
|
||||
|
||||
props = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_variant_unref);
|
||||
populate_passwd_props (props, pwd);
|
||||
|
||||
g_dbus_connection_register_object (connection, "/user", &user_interface,
|
||||
&user_vtable, props, (GDestroyNotify)g_hash_table_unref,
|
||||
&error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_critical ("couldn't register user object: %s", error->message);
|
||||
g_hash_table_unref (props);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_object_unref (connection);
|
||||
}
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdisksamples.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct {
|
||||
guint64 disk_read;
|
||||
guint64 disk_write;
|
||||
} cgroup_values_t;
|
||||
|
||||
/* TODO: this should be optimized so we don't allocate network and call open()/close() all the time */
|
||||
|
||||
void
|
||||
cockpit_disk_samples (CockpitSamples *samples)
|
||||
{
|
||||
gchar *contents = NULL;
|
||||
GError *error = NULL;
|
||||
gchar **lines = NULL;
|
||||
guint64 bytes_read;
|
||||
guint64 bytes_written;
|
||||
gsize len;
|
||||
guint n;
|
||||
static gboolean not_supported = FALSE;
|
||||
|
||||
if (not_supported)
|
||||
goto out;
|
||||
|
||||
if (!g_file_get_contents ("/proc/diskstats", &contents, &len, &error))
|
||||
{
|
||||
g_message ("error loading contents /proc/diskstats: %s", error->message);
|
||||
g_error_free (error);
|
||||
not_supported = TRUE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
bytes_read = 0;
|
||||
bytes_written = 0;
|
||||
|
||||
lines = g_strsplit (contents, "\n", -1);
|
||||
for (n = 0; lines != NULL && lines[n] != NULL; n++)
|
||||
{
|
||||
const gchar *line = lines[n];
|
||||
guint num_parsed;
|
||||
gint dev_major, dev_minor;
|
||||
gchar dev_name[128];
|
||||
guint64 num_reads, num_reads_merged, num_sectors_read, num_msec_reading;
|
||||
guint64 num_writes, num_writes_merged, num_sectors_written, num_msec_writing;
|
||||
guint64 num_io_in_progress, num_msec_doing_io, weighted_num_msec_doing_io;
|
||||
|
||||
if (strlen (line) == 0)
|
||||
continue;
|
||||
|
||||
/* From http://www.kernel.org/doc/Documentation/iostats.txt
|
||||
*
|
||||
* Field 1 -- # of reads completed
|
||||
* This is the total number of reads completed successfully.
|
||||
* Field 2 -- # of reads merged, field 6 -- # of writes merged
|
||||
* Reads and writes which are adjacent to each other may be merged for
|
||||
* efficiency. Thus two 4K reads may become one 8K read before it is
|
||||
* ultimately handed to the disk, and so it will be counted (and queued)
|
||||
* as only one I/O. This field lets you know how often this was done.
|
||||
* Field 3 -- # of sectors read
|
||||
* This is the total number of sectors read successfully.
|
||||
* Field 4 -- # of milliseconds spent reading
|
||||
* This is the total number of milliseconds spent by all reads (as
|
||||
* measured from __make_request() to end_that_request_last()).
|
||||
* Field 5 -- # of writes completed
|
||||
* This is the total number of writes completed successfully.
|
||||
* Field 7 -- # of sectors written
|
||||
* This is the total number of sectors written successfully.
|
||||
* Field 8 -- # of milliseconds spent writing
|
||||
* This is the total number of milliseconds spent by all writes (as
|
||||
* measured from __make_request() to end_that_request_last()).
|
||||
* Field 9 -- # of I/Os currently in progress
|
||||
* The only field that should go to zero. Incremented as requests are
|
||||
* given to appropriate struct request_queue and decremented as they finish.
|
||||
* Field 10 -- # of milliseconds spent doing I/Os
|
||||
* This field increases so long as field 9 is nonzero.
|
||||
* Field 11 -- weighted # of milliseconds spent doing I/Os
|
||||
* This field is incremented at each I/O start, I/O completion, I/O
|
||||
* merge, or read of these stats by the number of I/Os in progress
|
||||
* (field 9) times the number of milliseconds spent doing I/O since the
|
||||
* last update of this field. This can provide an easy measure of both
|
||||
* I/O completion time and the backlog that may be accumulating.
|
||||
*/
|
||||
|
||||
num_parsed = sscanf (line,
|
||||
"%d %d %127s"
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT,
|
||||
&dev_major, &dev_minor, dev_name,
|
||||
&num_reads, &num_reads_merged, &num_sectors_read, &num_msec_reading,
|
||||
&num_writes, &num_writes_merged, &num_sectors_written, &num_msec_writing,
|
||||
&num_io_in_progress, &num_msec_doing_io, &weighted_num_msec_doing_io);
|
||||
if (num_parsed != 14)
|
||||
{
|
||||
g_warning ("Error parsing line %d of file /proc/diskstats (num_parsed=%d): `%s'", n, num_parsed, line);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* skip mapped devices and partitions... otherwise we'll count their
|
||||
* I/O more than once
|
||||
*
|
||||
* TODO: the way we identify dm devices and partitions is not
|
||||
* very elegant... we should consult sysfs via libgudev1
|
||||
* instead.
|
||||
*/
|
||||
if (dev_major == 253 /* device-mapper */
|
||||
|| dev_major == 9) /* md */
|
||||
continue;
|
||||
|
||||
if ((g_str_has_prefix (dev_name, "sd")
|
||||
|| g_str_has_prefix (dev_name, "hd")
|
||||
|| g_str_has_prefix (dev_name, "vd"))
|
||||
&& g_ascii_isdigit (dev_name[strlen (dev_name) - 1]))
|
||||
continue;
|
||||
|
||||
// ignore nvme partitions
|
||||
if (g_str_has_prefix (dev_name, "nvme") && g_strrstr (dev_name, "p"))
|
||||
continue;
|
||||
|
||||
bytes_read += num_sectors_read * 512;
|
||||
bytes_written += num_sectors_written * 512;
|
||||
cockpit_samples_sample (samples, "disk.dev.read", dev_name, num_sectors_read * 512);
|
||||
cockpit_samples_sample (samples, "disk.dev.written", dev_name, num_sectors_written * 512);
|
||||
}
|
||||
|
||||
cockpit_samples_sample (samples, "disk.all.read", NULL, bytes_read);
|
||||
cockpit_samples_sample (samples, "disk.all.written", NULL, bytes_written);
|
||||
|
||||
out:
|
||||
g_strfreev (lines);
|
||||
g_free (contents);
|
||||
}
|
||||
|
||||
static FILE *
|
||||
open_file (int dirfd,
|
||||
const char *name)
|
||||
{
|
||||
const int fd = openat (dirfd, name, O_RDONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
if (errno != EACCES && errno != ESRCH && errno != ENOENT)
|
||||
g_message ("error opening file descriptor: %s: %m", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FILE *fp = fdopen (fd, "r");
|
||||
if (!fp)
|
||||
{
|
||||
if (errno != ESRCH && errno != ENOENT)
|
||||
g_message ("error opening file %s: %m", name);
|
||||
|
||||
close (fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
static void
|
||||
table_add_values(GHashTable *table,
|
||||
const gchar *cgroup,
|
||||
guint64 disk_read,
|
||||
guint64 disk_write)
|
||||
{
|
||||
cgroup_values_t *values = g_hash_table_lookup (table, cgroup);
|
||||
if (values == NULL)
|
||||
{
|
||||
values = g_new0(cgroup_values_t, 1);
|
||||
g_hash_table_insert (table, g_strdup (cgroup), values);
|
||||
}
|
||||
|
||||
values->disk_read += disk_read;
|
||||
values->disk_write += disk_write;
|
||||
}
|
||||
|
||||
static void
|
||||
get_process_io (const int dirfd,
|
||||
GHashTable *table)
|
||||
{
|
||||
FILE *io_fp = open_file (dirfd, "io");
|
||||
if (!io_fp)
|
||||
return;
|
||||
|
||||
gchar *key;
|
||||
guint64 disk_read = 0, disk_write = 0, value = 0;
|
||||
while (fscanf (io_fp, "%m[^: ]: %" G_GUINT64_FORMAT "\n", &key, &value) == 2)
|
||||
{
|
||||
if (g_str_equal (key, "read_bytes"))
|
||||
disk_read = value;
|
||||
else if (g_str_equal (key, "write_bytes"))
|
||||
disk_write = value;
|
||||
|
||||
free (key);
|
||||
}
|
||||
|
||||
fclose (io_fp);
|
||||
|
||||
// get process cgroup
|
||||
FILE *cgroup_fp = open_file (dirfd, "cgroup");
|
||||
if (!cgroup_fp)
|
||||
return;
|
||||
|
||||
gchar *cgroup = NULL;
|
||||
if (fscanf (cgroup_fp, "%ms\n", &cgroup) != 1)
|
||||
g_debug ("Failed to read cgroup name: %m");
|
||||
else
|
||||
table_add_values (table, cgroup, disk_read, disk_write);
|
||||
|
||||
g_free (cgroup);
|
||||
fclose (cgroup_fp);
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_cgroup_disk_usage (CockpitSamples *samples)
|
||||
{
|
||||
DIR *d = opendir ("/proc");
|
||||
if (!d)
|
||||
{
|
||||
g_warning ("Error when opening /proc, %m");
|
||||
return;
|
||||
}
|
||||
int proc_fd = dirfd (d);
|
||||
|
||||
GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
struct dirent *de;
|
||||
while ((de = readdir (d)))
|
||||
{
|
||||
// non-pid entries in proc are guaranteed to start with a character a-z
|
||||
if (de->d_name[0] < '0' || '9' < de->d_name[0])
|
||||
continue;
|
||||
|
||||
int dfd = openat(proc_fd, de->d_name, O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (dfd < 0)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
g_message ("failed to open /proc/%s, %m", de->d_name);
|
||||
continue;
|
||||
}
|
||||
get_process_io (dfd, table);
|
||||
close (dfd);
|
||||
}
|
||||
|
||||
closedir (d);
|
||||
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
g_hash_table_iter_init (&iter, table);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||||
{
|
||||
const gchar *cgroup_name = key;
|
||||
const cgroup_values_t *values = value;
|
||||
|
||||
// Skip ::0/
|
||||
cgroup_name += 4;
|
||||
|
||||
cockpit_samples_sample ((CockpitSamples *)samples, "disk.cgroup.read", cgroup_name, values->disk_read);
|
||||
cockpit_samples_sample ((CockpitSamples *)samples, "disk.cgroup.written", cgroup_name, values->disk_write);
|
||||
}
|
||||
g_hash_table_unref (table);
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_DISK_SAMPLES_H__
|
||||
#define COCKPIT_DISK_SAMPLES_H__
|
||||
|
||||
#include "cockpitsamples.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void cockpit_disk_samples (CockpitSamples *samples);
|
||||
|
||||
void cockpit_cgroup_disk_usage (CockpitSamples *samples);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_DISK_SAMPLES_H__ */
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitechochannel.h"
|
||||
|
||||
/**
|
||||
* CockpitEchoChannel:
|
||||
*
|
||||
* A #CockpitChannel that never sends messages and ignores
|
||||
* all messages it receives.
|
||||
*
|
||||
* The payload type for this channel is 'echo'.
|
||||
*/
|
||||
|
||||
#define COCKPIT_ECHO_CHANNEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_ECHO_CHANNEL, CockpitEchoChannel))
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
} CockpitEchoChannel;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannelClass parent_class;
|
||||
} CockpitEchoChannelClass;
|
||||
|
||||
G_DEFINE_TYPE (CockpitEchoChannel, cockpit_echo_channel, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
cockpit_echo_channel_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
g_debug ("received echo channel payload");
|
||||
cockpit_channel_send (channel, message, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cockpit_echo_channel_control (CockpitChannel *channel,
|
||||
const gchar *command,
|
||||
JsonObject *options)
|
||||
{
|
||||
if (g_str_equal (command, "done"))
|
||||
{
|
||||
g_debug ("received echo channel done");
|
||||
cockpit_channel_control (channel, command, options);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_echo_channel_init (CockpitEchoChannel *self)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_echo_channel_prepare (CockpitChannel *channel)
|
||||
{
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_echo_channel_parent_class)->prepare (channel);
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_echo_channel_class_init (CockpitEchoChannelClass *klass)
|
||||
{
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
channel_class->prepare = cockpit_echo_channel_prepare;
|
||||
channel_class->control = cockpit_echo_channel_control;
|
||||
channel_class->recv = cockpit_echo_channel_recv;
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_ECHO_CHANNEL_H__
|
||||
#define COCKPIT_ECHO_CHANNEL_H__
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_ECHO_CHANNEL (cockpit_echo_channel_get_type ())
|
||||
|
||||
GType cockpit_echo_channel_get_type (void) G_GNUC_CONST;
|
||||
|
||||
#endif /* COCKPIT_ECHO_CHANEL_H__ */
|
||||
|
|
@ -1,358 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitfslist.h"
|
||||
#include "cockpitfswatch.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* CockpitFslist:
|
||||
*
|
||||
* A #CockpitChannel that lists and optionally watches a directory.
|
||||
*
|
||||
* The payload type for this channel is 'fslist1'.
|
||||
*/
|
||||
|
||||
#define COCKPIT_FSLIST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_FSLIST, CockpitFslist))
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
const gchar *path;
|
||||
GFileMonitor *monitor;
|
||||
guint sig_changed;
|
||||
GCancellable *cancellable;
|
||||
} CockpitFslist;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannelClass parent_class;
|
||||
} CockpitFslistClass;
|
||||
|
||||
G_DEFINE_TYPE (CockpitFslist, cockpit_fslist, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
cockpit_fslist_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "Received unexpected message in fslist1 channel");
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fslist_init (CockpitFslist *self)
|
||||
{
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
error_to_problem (GError *error)
|
||||
{
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
|
||||
return "access-denied";
|
||||
else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||||
return "not-found";
|
||||
else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
|
||||
return "not-found";
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
on_files_listed (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
JsonObject *options;
|
||||
GList *files;
|
||||
|
||||
files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source_object), res, &error);
|
||||
if (error)
|
||||
{
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
{
|
||||
CockpitFslist *self = COCKPIT_FSLIST (user_data);
|
||||
g_message ("%s: couldn't process files %s", COCKPIT_FSLIST(user_data)->path, error->message);
|
||||
options = cockpit_channel_close_options (COCKPIT_CHANNEL (self));
|
||||
json_object_set_string_member (options, "message", error->message);
|
||||
cockpit_channel_close (COCKPIT_CHANNEL (self), "internal-error");
|
||||
}
|
||||
g_clear_error (&error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
CockpitFslist *self = COCKPIT_FSLIST (user_data);
|
||||
|
||||
if (files == NULL)
|
||||
{
|
||||
g_clear_object (&self->cancellable);
|
||||
g_object_unref (source_object);
|
||||
|
||||
cockpit_channel_ready (COCKPIT_CHANNEL (self), NULL);
|
||||
|
||||
if (self->monitor == NULL)
|
||||
{
|
||||
cockpit_channel_control (COCKPIT_CHANNEL (self), "done", NULL);
|
||||
cockpit_channel_close (COCKPIT_CHANNEL (self), NULL);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (GList *l = files; l; l = l->next)
|
||||
{
|
||||
GFileInfo *info = G_FILE_INFO (l->data);
|
||||
JsonObject *msg;
|
||||
GBytes *msg_bytes;
|
||||
|
||||
msg = json_object_new ();
|
||||
json_object_set_string_member (msg, "event", "present");
|
||||
json_object_set_string_member
|
||||
(msg, "path", g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_STANDARD_NAME));
|
||||
json_object_set_string_member
|
||||
(msg, "type", cockpit_file_type_to_string (g_file_info_get_file_type (info)));
|
||||
json_object_set_string_member
|
||||
(msg, "owner", g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER));
|
||||
json_object_set_string_member
|
||||
(msg, "group", g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP));
|
||||
json_object_set_int_member
|
||||
(msg, "size", g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE));
|
||||
json_object_set_int_member
|
||||
(msg, "modified", g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED));
|
||||
msg_bytes = cockpit_json_write_bytes (msg);
|
||||
json_object_unref (msg);
|
||||
cockpit_channel_send (COCKPIT_CHANNEL(self), msg_bytes, FALSE);
|
||||
g_bytes_unref (msg_bytes);
|
||||
}
|
||||
|
||||
g_list_free_full (files, g_object_unref);
|
||||
|
||||
g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (source_object),
|
||||
10,
|
||||
G_PRIORITY_DEFAULT,
|
||||
self->cancellable,
|
||||
on_files_listed,
|
||||
g_object_ref (self));
|
||||
out:
|
||||
g_object_unref (user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
on_enumerator_ready (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GFileEnumerator *enumerator;
|
||||
JsonObject *options;
|
||||
const gchar *problem;
|
||||
|
||||
enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, &error);
|
||||
if (enumerator == NULL)
|
||||
{
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
{
|
||||
CockpitFslist *self = COCKPIT_FSLIST (user_data);
|
||||
problem = error_to_problem (error);
|
||||
if (problem)
|
||||
g_debug ("%s: couldn't list directory: %s", self->path, error->message);
|
||||
else
|
||||
g_warning ("%s: couldn't list directory: %s", self->path, error->message);
|
||||
options = cockpit_channel_close_options (COCKPIT_CHANNEL (self));
|
||||
json_object_set_string_member (options, "message", error->message);
|
||||
cockpit_channel_close (COCKPIT_CHANNEL (self), problem ? problem : "internal-error");
|
||||
}
|
||||
g_clear_error (&error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
CockpitFslist *self = COCKPIT_FSLIST (user_data);
|
||||
|
||||
g_file_enumerator_next_files_async (enumerator,
|
||||
10,
|
||||
G_PRIORITY_DEFAULT,
|
||||
self->cancellable,
|
||||
on_files_listed,
|
||||
g_object_ref (self));
|
||||
out:
|
||||
g_object_unref (user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
on_changed (GFileMonitor *monitor,
|
||||
GFile *file,
|
||||
GFile *other_file,
|
||||
GFileMonitorEvent event_type,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitFslist *self = COCKPIT_FSLIST(user_data);
|
||||
cockpit_fswatch_emit_event (COCKPIT_CHANNEL(self), file, other_file, event_type);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fslist_prepare (CockpitChannel *channel)
|
||||
{
|
||||
CockpitFslist *self = COCKPIT_FSLIST (channel);
|
||||
JsonObject *options;
|
||||
GError *error = NULL;
|
||||
GFile *file = NULL;
|
||||
gboolean watch;
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_fslist_parent_class)->prepare (channel);
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
if (!cockpit_json_get_string (options, "path", NULL, &self->path))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"path\" option for fslist1 channel");
|
||||
goto out;
|
||||
}
|
||||
if (self->path == NULL || *(self->path) == 0)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "missing \"path\" option for fslist1 channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_bool (options, "watch", TRUE, &watch))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"watch\" option for fslist1 channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
self->cancellable = g_cancellable_new ();
|
||||
|
||||
file = g_file_new_for_path (self->path);
|
||||
|
||||
if (watch)
|
||||
{
|
||||
self->monitor = g_file_monitor_directory (file, 0, NULL, &error);
|
||||
if (self->monitor == NULL)
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"%s: couldn't monitor directory: %s", self->path, error->message);
|
||||
goto out;
|
||||
}
|
||||
|
||||
self->sig_changed = g_signal_connect (self->monitor, "changed", G_CALLBACK (on_changed), self);
|
||||
}
|
||||
|
||||
g_file_enumerate_children_async (file,
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE ","
|
||||
G_FILE_ATTRIBUTE_OWNER_USER "," G_FILE_ATTRIBUTE_OWNER_GROUP ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_TIME_MODIFIED,
|
||||
G_FILE_QUERY_INFO_NONE,
|
||||
G_PRIORITY_DEFAULT,
|
||||
self->cancellable,
|
||||
on_enumerator_ready,
|
||||
g_object_ref (self));
|
||||
out:
|
||||
g_clear_error (&error);
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fslist_dispose (GObject *object)
|
||||
{
|
||||
CockpitFslist *self = COCKPIT_FSLIST (object);
|
||||
|
||||
if (self->cancellable)
|
||||
g_cancellable_cancel (self->cancellable);
|
||||
|
||||
if (self->monitor)
|
||||
{
|
||||
if (self->sig_changed)
|
||||
g_signal_handler_disconnect (self->monitor, self->sig_changed);
|
||||
self->sig_changed = 0;
|
||||
|
||||
/* HACK - It is not generally safe to just unref a GFileMonitor:
|
||||
* https://gitlab.gnome.org/GNOME/glib/issues/1941
|
||||
*/
|
||||
g_file_monitor_cancel (self->monitor);
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (cockpit_fslist_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fslist_finalize (GObject *object)
|
||||
{
|
||||
CockpitFslist *self = COCKPIT_FSLIST (object);
|
||||
|
||||
g_clear_object (&self->cancellable);
|
||||
g_clear_object (&self->monitor);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_fslist_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fslist_class_init (CockpitFslistClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = cockpit_fslist_dispose;
|
||||
gobject_class->finalize = cockpit_fslist_finalize;
|
||||
|
||||
channel_class->prepare = cockpit_fslist_prepare;
|
||||
channel_class->recv = cockpit_fslist_recv;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_fslist_open:
|
||||
* @transport: the transport to send/receive messages on
|
||||
* @channel_id: the channel id
|
||||
* @path: the path name of the file to read
|
||||
* @watch: boolean, watch the directory as well?
|
||||
*
|
||||
* This function is mainly used by tests. The usual way
|
||||
* to get a #CockpitFslist is via cockpit_channel_open()
|
||||
*
|
||||
* Returns: (transfer full): the new channel
|
||||
*/
|
||||
CockpitChannel *
|
||||
cockpit_fslist_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *path,
|
||||
const gboolean watch)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
|
||||
g_return_val_if_fail (channel_id != NULL, NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_string_member (options, "path", path);
|
||||
json_object_set_string_member (options, "payload", "fslist1");
|
||||
json_object_set_boolean_member (options, "watch", watch);
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_FSLIST,
|
||||
"transport", transport,
|
||||
"id", channel_id,
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
return channel;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_FSLIST_H__
|
||||
#define COCKPIT_FSLIST_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_FSLIST (cockpit_fslist_get_type ())
|
||||
|
||||
GType cockpit_fslist_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitChannel * cockpit_fslist_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *path,
|
||||
const gboolean watch);
|
||||
|
||||
#endif /* COCKPIT_FSLIST_H__ */
|
||||
|
|
@ -1,382 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitfsread.h"
|
||||
|
||||
#include "common/cockpitflow.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitpipe.h"
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DEFAULT_MAX_READ_SIZE (16*1024*1024)
|
||||
|
||||
/**
|
||||
* CockpitFsread:
|
||||
*
|
||||
* A #CockpitChannel that reads the content of a file.
|
||||
*
|
||||
* The payload type for this channel is 'fsread1'.
|
||||
*/
|
||||
|
||||
#define COCKPIT_FSREAD(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_FSREAD, CockpitFsread))
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
const gchar *path;
|
||||
gchar *start_tag;
|
||||
int fd;
|
||||
|
||||
CockpitPipe *pipe;
|
||||
gboolean open;
|
||||
gboolean closing;
|
||||
guint sig_read;
|
||||
guint sig_close;
|
||||
} CockpitFsread;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannelClass parent_class;
|
||||
} CockpitFsreadClass;
|
||||
|
||||
G_DEFINE_TYPE (CockpitFsread, cockpit_fsread, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
cockpit_fsread_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "received unexpected message in fsread channel");
|
||||
}
|
||||
|
||||
static gchar *
|
||||
file_tag_from_stat (int res,
|
||||
int err,
|
||||
struct stat *buf)
|
||||
{
|
||||
// The transaction tag is the inode and mtime of the file. Mtime is
|
||||
// used to catch in-place modifications, and the inode to catch
|
||||
// renames.
|
||||
|
||||
if (res >= 0)
|
||||
return g_strdup_printf ("1:%lu-%lld.%ld",
|
||||
(unsigned long)buf->st_ino,
|
||||
(long long int)buf->st_mtim.tv_sec,
|
||||
(long int)buf->st_mtim.tv_nsec);
|
||||
else if (err == ENOENT)
|
||||
return g_strdup ("-");
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gchar *
|
||||
cockpit_get_file_tag (const gchar *path)
|
||||
{
|
||||
struct stat buf;
|
||||
int res = stat (path, &buf);
|
||||
return file_tag_from_stat (res, errno, &buf);
|
||||
}
|
||||
|
||||
gchar *
|
||||
cockpit_get_file_tag_from_fd (int fd)
|
||||
{
|
||||
struct stat buf;
|
||||
int res = fstat (fd, &buf);
|
||||
return file_tag_from_stat (res, errno, &buf);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsread_close (CockpitChannel *channel,
|
||||
const gchar *problem)
|
||||
{
|
||||
CockpitFsread *self = COCKPIT_FSREAD (channel);
|
||||
|
||||
self->closing = TRUE;
|
||||
|
||||
/*
|
||||
* If closed, call base class handler directly. Otherwise ask
|
||||
* our pipe to close first, which will come back here.
|
||||
*/
|
||||
if (self->open)
|
||||
cockpit_pipe_close (self->pipe, problem);
|
||||
else
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_fsread_parent_class)->close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsread_init (CockpitFsread *self)
|
||||
{
|
||||
self->fd = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
on_pipe_read (CockpitPipe *pipe,
|
||||
GByteArray *data,
|
||||
gboolean end_of_data,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitFsread *self = user_data;
|
||||
CockpitChannel *channel = user_data;
|
||||
const gchar *problem;
|
||||
JsonObject *options;
|
||||
GBytes *message;
|
||||
gchar *tag;
|
||||
|
||||
if (data->len)
|
||||
{
|
||||
/* When array is reffed, this just clears byte array */
|
||||
g_byte_array_ref (data);
|
||||
message = g_byte_array_free_to_bytes (data);
|
||||
cockpit_channel_send (channel, message, FALSE);
|
||||
g_bytes_unref (message);
|
||||
}
|
||||
|
||||
if (end_of_data)
|
||||
{
|
||||
cockpit_channel_control (channel, "done", NULL);
|
||||
|
||||
problem = NULL;
|
||||
if (self->fd >= 0 && self->start_tag)
|
||||
{
|
||||
tag = cockpit_get_file_tag_from_fd (self->fd);
|
||||
if (g_strcmp0 (tag, self->start_tag) == 0)
|
||||
{
|
||||
options = cockpit_channel_close_options (channel);
|
||||
json_object_set_string_member (options, "tag", tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
problem = "change-conflict";
|
||||
}
|
||||
g_free (tag);
|
||||
}
|
||||
|
||||
cockpit_channel_close (channel, problem);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_pipe_close (CockpitPipe *pipe,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitFsread *self = COCKPIT_FSREAD (user_data);
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (user_data);
|
||||
|
||||
self->open = FALSE;
|
||||
cockpit_channel_close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsread_prepare (CockpitChannel *channel)
|
||||
{
|
||||
CockpitFsread *self = COCKPIT_FSREAD (channel);
|
||||
JsonObject *options;
|
||||
gint64 max_read_size;
|
||||
struct stat statbuf;
|
||||
mode_t ifmt;
|
||||
int fd;
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_fsread_parent_class)->prepare (channel);
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
|
||||
if (!cockpit_json_get_string (options, "path", NULL, &self->path))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"path\" option for fsread channel");
|
||||
return;
|
||||
}
|
||||
if (self->path == NULL || *(self->path) == 0)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "missing \"path\" option for fsread channel");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_int (options, "max_read_size", DEFAULT_MAX_READ_SIZE, &max_read_size))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"max_read_size\" option for fsread channel");
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->closing)
|
||||
return;
|
||||
|
||||
fd = open (self->path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
int err = errno;
|
||||
if (err == ENOENT)
|
||||
{
|
||||
options = cockpit_channel_close_options (channel);
|
||||
json_object_set_string_member (options, "tag", "-");
|
||||
cockpit_channel_close (channel, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (err == EPERM || err == EACCES)
|
||||
{
|
||||
g_debug ("%s: couldn't open: %s", self->path, strerror (err));
|
||||
cockpit_channel_close (channel, "access-denied");
|
||||
}
|
||||
else
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"%s: couldn't open: %s", self->path, strerror (err));
|
||||
}
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fstat (fd, &statbuf) < 0)
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error", "%s: couldn't stat: %s", self->path, strerror (errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
ifmt = (statbuf.st_mode & S_IFMT);
|
||||
if (ifmt != S_IFREG && ifmt != S_IFBLK)
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error", "%s: not a readable file", self->path);
|
||||
goto out;
|
||||
}
|
||||
if (ifmt == S_IFREG && statbuf.st_size > max_read_size)
|
||||
{
|
||||
cockpit_channel_close (channel, "too-large");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* This owns the file descriptor */
|
||||
self->pipe = cockpit_pipe_new (self->path, fd, -1);
|
||||
self->fd = fd;
|
||||
fd = -1;
|
||||
|
||||
self->start_tag = cockpit_get_file_tag_from_fd (self->fd);
|
||||
|
||||
/* Let the channel throttle the pipe's input flow*/
|
||||
cockpit_flow_throttle (COCKPIT_FLOW (self->pipe), COCKPIT_FLOW (self));
|
||||
|
||||
/* Let the pipe input the channel peer's output flow */
|
||||
cockpit_flow_throttle (COCKPIT_FLOW (channel), COCKPIT_FLOW (self->pipe));
|
||||
|
||||
self->sig_read = g_signal_connect (self->pipe, "read", G_CALLBACK (on_pipe_read), self);
|
||||
self->sig_close = g_signal_connect (self->pipe, "close", G_CALLBACK (on_pipe_close), self);
|
||||
|
||||
const gchar *binary;
|
||||
if (S_ISREG(statbuf.st_mode) && cockpit_json_get_string (options, "binary", "", &binary) && g_str_equal (binary, "raw"))
|
||||
{
|
||||
g_autoptr(JsonObject) message = json_object_new ();
|
||||
json_object_set_int_member (message, "size-hint", statbuf.st_size);
|
||||
cockpit_channel_ready (channel, message);
|
||||
} else {
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
}
|
||||
|
||||
|
||||
out:
|
||||
if (fd >= 0)
|
||||
close (fd);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsread_dispose (GObject *object)
|
||||
{
|
||||
CockpitFsread *self = COCKPIT_FSREAD (object);
|
||||
|
||||
if (self->pipe)
|
||||
{
|
||||
if (self->open)
|
||||
cockpit_pipe_close (self->pipe, "terminated");
|
||||
if (self->sig_read)
|
||||
g_signal_handler_disconnect (self->pipe, self->sig_read);
|
||||
if (self->sig_close)
|
||||
g_signal_handler_disconnect (self->pipe, self->sig_close);
|
||||
self->sig_read = self->sig_close = 0;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (cockpit_fsread_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsread_finalize (GObject *object)
|
||||
{
|
||||
CockpitFsread *self = COCKPIT_FSREAD (object);
|
||||
|
||||
g_free (self->start_tag);
|
||||
g_clear_object (&self->pipe);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_fsread_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsread_class_init (CockpitFsreadClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = cockpit_fsread_dispose;
|
||||
gobject_class->finalize = cockpit_fsread_finalize;
|
||||
|
||||
channel_class->prepare = cockpit_fsread_prepare;
|
||||
channel_class->recv = cockpit_fsread_recv;
|
||||
channel_class->close = cockpit_fsread_close;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_fsread_open:
|
||||
* @transport: the transport to send/receive messages on
|
||||
* @channel_id: the channel id
|
||||
* @path: the path name of the file to read
|
||||
* @binary: set binary to "raw"
|
||||
*
|
||||
* This function is mainly used by tests. The usual way
|
||||
* to get a #CockpitFsread is via cockpit_channel_open()
|
||||
*
|
||||
* Returns: (transfer full): the new channel
|
||||
*/
|
||||
CockpitChannel *
|
||||
cockpit_fsread_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *path,
|
||||
gboolean binary)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
|
||||
g_return_val_if_fail (channel_id != NULL, NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_string_member (options, "path", path);
|
||||
json_object_set_string_member (options, "payload", "fsread1");
|
||||
if (binary)
|
||||
json_object_set_string_member (options, "binary", "raw");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_FSREAD,
|
||||
"transport", transport,
|
||||
"id", channel_id,
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
return channel;
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_FSREAD_H__
|
||||
#define COCKPIT_FSREAD_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_FSREAD (cockpit_fsread_get_type ())
|
||||
|
||||
GType cockpit_fsread_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitChannel * cockpit_fsread_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *path,
|
||||
gboolean binary);
|
||||
|
||||
gchar * cockpit_get_file_tag (const gchar *path);
|
||||
|
||||
gchar * cockpit_get_file_tag_from_fd (int fd);
|
||||
|
||||
#endif /* COCKPIT_FSREAD_H__ */
|
||||
|
|
@ -1,347 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitfsreplace.h"
|
||||
#include "cockpitfsread.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* CockpitFsreplace:
|
||||
*
|
||||
* A #CockpitChannel that writes/replaces the content of a file.
|
||||
*
|
||||
* The payload type for this channel is 'fsreplace1'.
|
||||
*/
|
||||
|
||||
#define COCKPIT_FSREPLACE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_FSREPLACE, CockpitFsreplace))
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
const gchar *path;
|
||||
gchar *tmp_path;
|
||||
int fd;
|
||||
gboolean got_content;
|
||||
const gchar *expected_tag;
|
||||
guint sig_close;
|
||||
} CockpitFsreplace;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannelClass parent_class;
|
||||
} CockpitFsreplaceClass;
|
||||
|
||||
G_DEFINE_TYPE (CockpitFsreplace, cockpit_fsreplace, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
close_with_errno (CockpitFsreplace *self,
|
||||
const gchar *diagnostic,
|
||||
int err)
|
||||
{
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (self);
|
||||
|
||||
if (err == EPERM || err == EACCES)
|
||||
{
|
||||
g_debug ("%s: %s: %s", self->path, diagnostic, strerror (err));
|
||||
cockpit_channel_close (channel, "access-denied");
|
||||
}
|
||||
else
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error",
|
||||
"%s: %s: %s", self->path, diagnostic, strerror (err));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsreplace_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
CockpitFsreplace *self = COCKPIT_FSREPLACE (channel);
|
||||
gsize size;
|
||||
const char *data = g_bytes_get_data (message, &size);
|
||||
|
||||
self->got_content = TRUE;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
ssize_t n = write (self->fd, data, size);
|
||||
if (n < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
close_with_errno (self, "couldn't write", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
g_return_if_fail (n > 0);
|
||||
size -= n;
|
||||
data += n;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
xfsync (int fd)
|
||||
{
|
||||
while (TRUE)
|
||||
{
|
||||
int res = fsync (fd);
|
||||
if (res < 0 && errno == EINTR)
|
||||
continue;
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
xclose (int fd)
|
||||
{
|
||||
/* http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
|
||||
*/
|
||||
int res = close (fd);
|
||||
if (res < 0 && errno == EINTR)
|
||||
return 0;
|
||||
else
|
||||
return res;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cockpit_fsreplace_control (CockpitChannel *channel,
|
||||
const gchar *command,
|
||||
JsonObject *unused)
|
||||
{
|
||||
CockpitFsreplace *self = COCKPIT_FSREPLACE (channel);
|
||||
gchar *actual_tag = NULL;
|
||||
gchar *new_tag = NULL;
|
||||
JsonObject *options;
|
||||
|
||||
if (!g_str_equal (command, "done"))
|
||||
return FALSE;
|
||||
|
||||
g_object_ref (self);
|
||||
|
||||
/* Commit the changes when there was no problem */
|
||||
if (xfsync (self->fd) < 0 || xclose (self->fd) < 0)
|
||||
{
|
||||
close_with_errno (self, "couldn't sync", errno);
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
actual_tag = cockpit_get_file_tag (self->path);
|
||||
if (self->expected_tag && g_strcmp0 (self->expected_tag, actual_tag))
|
||||
{
|
||||
cockpit_channel_close (channel, "out-of-date");
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
options = cockpit_channel_close_options (channel);
|
||||
if (!self->got_content)
|
||||
{
|
||||
json_object_set_string_member (options, "tag", "-");
|
||||
if (unlink (self->tmp_path) < 0)
|
||||
g_message ("%s: couldn't remove temp file: %s", self->tmp_path, g_strerror (errno));
|
||||
if (unlink (self->path) < 0 && errno != ENOENT)
|
||||
{
|
||||
close_with_errno (self, "couldn't unlink", errno);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
new_tag = cockpit_get_file_tag (self->tmp_path);
|
||||
json_object_set_string_member (options, "tag", new_tag);
|
||||
if (rename (self->tmp_path, self->path) < 0)
|
||||
{
|
||||
close_with_errno (self, "couldn't rename", errno);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cockpit_channel_close (channel, NULL);
|
||||
|
||||
out:
|
||||
g_free (new_tag);
|
||||
g_free (actual_tag);
|
||||
self->fd = -1;
|
||||
g_object_unref (self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsreplace_close (CockpitChannel *channel,
|
||||
const gchar *problem)
|
||||
{
|
||||
CockpitFsreplace *self = COCKPIT_FSREPLACE (channel);
|
||||
|
||||
if (self->fd != -1)
|
||||
close (self->fd);
|
||||
self->fd = -1;
|
||||
|
||||
/* Cleanup in case of problem */
|
||||
if (problem)
|
||||
{
|
||||
if (self->tmp_path)
|
||||
if (unlink (self->tmp_path) < 0 && errno != ENOENT)
|
||||
g_message ("%s: couldn't remove temp file: %s", self->tmp_path, g_strerror (errno));
|
||||
}
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_fsreplace_parent_class)->close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsreplace_init (CockpitFsreplace *self)
|
||||
{
|
||||
self->fd = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsreplace_prepare (CockpitChannel *channel)
|
||||
{
|
||||
CockpitFsreplace *self = COCKPIT_FSREPLACE (channel);
|
||||
JsonObject *options;
|
||||
gchar *actual_tag = NULL;
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_fsreplace_parent_class)->prepare (channel);
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
if (!cockpit_json_get_string (options, "path", NULL, &self->path))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"path\" option for fsreplace1 channel");
|
||||
goto out;
|
||||
}
|
||||
else if (self->path == NULL || g_str_equal (self->path, ""))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"missing \"path\" option for fsreplace1 channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_string (options, "tag", NULL, &self->expected_tag))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"%s: invalid \"tag\" option for fsreplace1 channel", self->path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
actual_tag = cockpit_get_file_tag (self->path);
|
||||
if (self->expected_tag && g_strcmp0 (self->expected_tag, actual_tag))
|
||||
{
|
||||
cockpit_channel_close (channel, "change-conflict");
|
||||
goto out;
|
||||
}
|
||||
|
||||
// TODO - delay the opening until the first content message. That
|
||||
// way, we don't create a useless temporary file (which might even
|
||||
// fail).
|
||||
|
||||
for (int i = 1; i < 10000; i++)
|
||||
{
|
||||
self->tmp_path = g_strdup_printf ("%s.%d", self->path, i);
|
||||
self->fd = open (self->tmp_path, O_WRONLY | O_CREAT | O_EXCL, 0666);
|
||||
if (self->fd >= 0 || errno != EEXIST)
|
||||
break;
|
||||
g_free (self->tmp_path);
|
||||
self->tmp_path = NULL;
|
||||
}
|
||||
|
||||
if (self->fd < 0)
|
||||
close_with_errno (self, "couldn't open unique file", errno);
|
||||
else
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
|
||||
out:
|
||||
g_free (actual_tag);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsreplace_finalize (GObject *object)
|
||||
{
|
||||
CockpitFsreplace *self = COCKPIT_FSREPLACE (object);
|
||||
|
||||
g_free (self->tmp_path);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_fsreplace_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fsreplace_class_init (CockpitFsreplaceClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
gobject_class->finalize = cockpit_fsreplace_finalize;
|
||||
|
||||
channel_class->prepare = cockpit_fsreplace_prepare;
|
||||
channel_class->control = cockpit_fsreplace_control;
|
||||
channel_class->recv = cockpit_fsreplace_recv;
|
||||
channel_class->close = cockpit_fsreplace_close;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_fsreplace_open:
|
||||
* @transport: the transport to send/receive messages on
|
||||
* @channel_id: the channel id
|
||||
* @path: the path name of the file to write
|
||||
* @tag: the expected tag, or NULL
|
||||
*
|
||||
* This function is mainly used by tests. The usual way
|
||||
* to get a #CockpitFsreplace is via cockpit_channel_open()
|
||||
*
|
||||
* Returns: (transfer full): the new channel
|
||||
*/
|
||||
CockpitChannel *
|
||||
cockpit_fsreplace_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *path,
|
||||
const gchar *tag)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
|
||||
g_return_val_if_fail (channel_id != NULL, NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_string_member (options, "path", path);
|
||||
if (tag)
|
||||
json_object_set_string_member (options, "tag", tag);
|
||||
json_object_set_string_member (options, "payload", "fsreplace1");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_FSREPLACE,
|
||||
"transport", transport,
|
||||
"id", channel_id,
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
return channel;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_FSREPLACE_H__
|
||||
#define COCKPIT_FSREPLACE_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_FSREPLACE (cockpit_fsreplace_get_type ())
|
||||
|
||||
GType cockpit_fsreplace_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitChannel * cockpit_fsreplace_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *path,
|
||||
const gchar *tag);
|
||||
|
||||
#endif /* COCKPIT_FSREPLACE_H__ */
|
||||
|
|
@ -1,294 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitfswatch.h"
|
||||
#include "cockpitfsread.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* CockpitFswatch:
|
||||
*
|
||||
* A #CockpitChannel that watches a file or directory.
|
||||
*
|
||||
* The payload type for this channel is 'fswatch1'.
|
||||
*/
|
||||
|
||||
#define COCKPIT_FSWATCH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_FSWATCH, CockpitFswatch))
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
const gchar *path;
|
||||
GFileMonitor *monitor;
|
||||
guint sig_changed;
|
||||
} CockpitFswatch;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannelClass parent_class;
|
||||
} CockpitFswatchClass;
|
||||
|
||||
G_DEFINE_TYPE (CockpitFswatch, cockpit_fswatch, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
cockpit_fswatch_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"received unexpected message in fswatch channel");
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fswatch_init (CockpitFswatch *self)
|
||||
{
|
||||
}
|
||||
|
||||
gchar *
|
||||
cockpit_file_type_to_string (GFileType file_type)
|
||||
{
|
||||
switch (file_type) {
|
||||
case G_FILE_TYPE_REGULAR:
|
||||
return "file";
|
||||
case G_FILE_TYPE_DIRECTORY:
|
||||
return "directory";
|
||||
case G_FILE_TYPE_SYMBOLIC_LINK:
|
||||
return "link";
|
||||
case G_FILE_TYPE_SPECIAL:
|
||||
return "special";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static gchar *
|
||||
event_type_to_string (GFileMonitorEvent event_type)
|
||||
{
|
||||
switch (event_type) {
|
||||
case G_FILE_MONITOR_EVENT_CHANGED:
|
||||
return "changed";
|
||||
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
|
||||
return "done-hint";
|
||||
case G_FILE_MONITOR_EVENT_DELETED:
|
||||
return "deleted";
|
||||
case G_FILE_MONITOR_EVENT_CREATED:
|
||||
return "created";
|
||||
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
|
||||
return "attribute-changed";
|
||||
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
|
||||
return "pre-unmount";
|
||||
case G_FILE_MONITOR_EVENT_UNMOUNTED:
|
||||
return "unmounted";
|
||||
case G_FILE_MONITOR_EVENT_MOVED:
|
||||
return "moved";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_fswatch_emit_event (CockpitChannel *channel,
|
||||
GFile *file,
|
||||
GFile *other_file,
|
||||
GFileMonitorEvent event_type)
|
||||
{
|
||||
JsonObject *msg;
|
||||
GBytes *msg_bytes;
|
||||
|
||||
msg = json_object_new ();
|
||||
json_object_set_string_member (msg, "event", event_type_to_string (event_type));
|
||||
if (file)
|
||||
{
|
||||
char *p = g_file_get_path (file);
|
||||
char *t = cockpit_get_file_tag (p);
|
||||
json_object_set_string_member (msg, "path", p);
|
||||
json_object_set_string_member (msg, "tag", t);
|
||||
if (event_type == G_FILE_MONITOR_EVENT_CREATED)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GFileInfo *info = g_file_query_info (file,
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
NULL, &error);
|
||||
if (info)
|
||||
{
|
||||
json_object_set_string_member
|
||||
(msg, "type", cockpit_file_type_to_string (g_file_info_get_file_type (info)));
|
||||
g_object_unref (info);
|
||||
}
|
||||
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_free (p);
|
||||
g_free (t);
|
||||
}
|
||||
if (other_file)
|
||||
{
|
||||
char *p = g_file_get_path (other_file);
|
||||
json_object_set_string_member (msg, "other", p);
|
||||
g_free (p);
|
||||
}
|
||||
msg_bytes = cockpit_json_write_bytes (msg);
|
||||
json_object_unref (msg);
|
||||
cockpit_channel_send (channel, msg_bytes, TRUE);
|
||||
g_bytes_unref (msg_bytes);
|
||||
}
|
||||
|
||||
static void
|
||||
on_changed (GFileMonitor *monitor,
|
||||
GFile *file,
|
||||
GFile *other_file,
|
||||
GFileMonitorEvent event_type,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitFswatch *self = COCKPIT_FSWATCH (user_data);
|
||||
cockpit_fswatch_emit_event (COCKPIT_CHANNEL(self), file, other_file, event_type);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fswatch_prepare (CockpitChannel *channel)
|
||||
{
|
||||
CockpitFswatch *self = COCKPIT_FSWATCH (channel);
|
||||
JsonObject *options;
|
||||
GError *error = NULL;
|
||||
const gchar *path;
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_fswatch_parent_class)->prepare (channel);
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
if (!cockpit_json_get_string (options, "path", NULL, &path))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"path\" option for fswatch channel");
|
||||
goto out;
|
||||
}
|
||||
else if (path == NULL || *path == 0)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"missing \"path\" option for fswatch channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
GFile *file = g_file_new_for_path (path);
|
||||
GFileMonitor *monitor = g_file_monitor (file, 0, NULL, &error);
|
||||
g_object_unref (file);
|
||||
|
||||
if (monitor == NULL)
|
||||
{
|
||||
cockpit_channel_fail (channel, "internal-error", "%s: %s", self->path, error->message);
|
||||
goto out;
|
||||
}
|
||||
|
||||
self->monitor = monitor;
|
||||
self->sig_changed = g_signal_connect (self->monitor, "changed", G_CALLBACK (on_changed), self);
|
||||
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
|
||||
out:
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fswatch_dispose (GObject *object)
|
||||
{
|
||||
CockpitFswatch *self = COCKPIT_FSWATCH (object);
|
||||
|
||||
if (self->monitor)
|
||||
{
|
||||
if (self->sig_changed)
|
||||
g_signal_handler_disconnect (self->monitor, self->sig_changed);
|
||||
self->sig_changed = 0;
|
||||
|
||||
/* HACK - It is not generally safe to just unref a GFileMonitor:
|
||||
* https://gitlab.gnome.org/GNOME/glib/issues/1941
|
||||
*/
|
||||
g_file_monitor_cancel (self->monitor);
|
||||
|
||||
g_object_unref (self->monitor);
|
||||
self->monitor = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (cockpit_fswatch_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fswatch_finalize (GObject *object)
|
||||
{
|
||||
CockpitFswatch *self = COCKPIT_FSWATCH (object);
|
||||
|
||||
g_clear_object (&self->monitor);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_fswatch_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_fswatch_class_init (CockpitFswatchClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = cockpit_fswatch_dispose;
|
||||
gobject_class->finalize = cockpit_fswatch_finalize;
|
||||
|
||||
channel_class->prepare = cockpit_fswatch_prepare;
|
||||
channel_class->recv = cockpit_fswatch_recv;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_fswatch_open:
|
||||
* @transport: the transport to send/receive messages on
|
||||
* @channel_id: the channel id
|
||||
* @path: the path name of the file to read
|
||||
*
|
||||
* This function is mainly used by tests. The usual way
|
||||
* to get a #CockpitFswatch is via cockpit_channel_open()
|
||||
*
|
||||
* Returns: (transfer full): the new channel
|
||||
*/
|
||||
CockpitChannel *
|
||||
cockpit_fswatch_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *path)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
|
||||
g_return_val_if_fail (channel_id != NULL, NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_string_member (options, "path", path);
|
||||
json_object_set_string_member (options, "payload", "fswatch1");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_FSWATCH,
|
||||
"transport", transport,
|
||||
"id", channel_id,
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
return channel;
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_FSWATCH_H__
|
||||
#define COCKPIT_FSWATCH_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_FSWATCH (cockpit_fswatch_get_type ())
|
||||
|
||||
GType cockpit_fswatch_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitChannel * cockpit_fswatch_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *path);
|
||||
|
||||
gchar * cockpit_file_type_to_string (GFileType file_type);
|
||||
|
||||
void
|
||||
cockpit_fswatch_emit_event (CockpitChannel *channel,
|
||||
GFile *file,
|
||||
GFile *other_file,
|
||||
GFileMonitorEvent event_type);
|
||||
|
||||
#endif /* COCKPIT_FSWATCH_H__ */
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_HTTP_STREAM_H__
|
||||
#define COCKPIT_HTTP_STREAM_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_HTTP_STREAM (cockpit_http_stream_get_type ())
|
||||
|
||||
GType cockpit_http_stream_get_type (void) G_GNUC_CONST;
|
||||
|
||||
gboolean cockpit_http_stream_parse_keep_alive (const gchar *version,
|
||||
GHashTable *headers);
|
||||
|
||||
#endif /* COCKPIT_HTTP_STREAM_H__ */
|
||||
|
|
@ -1,289 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitinteracttransport.h"
|
||||
|
||||
#include "common/cockpitpipe.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* CockpitInteractTransport:
|
||||
*
|
||||
* A #CockpitTransport implementation that shuttles data over a
|
||||
* #CockpitPipe connected to stdio and handles framing in a way
|
||||
* that it's more usable for debugging channels.
|
||||
*/
|
||||
|
||||
struct _CockpitInteractTransport {
|
||||
CockpitTransport parent_instance;
|
||||
gchar *name;
|
||||
gchar *delimiter;
|
||||
gsize delimiter_len;
|
||||
gboolean colored;
|
||||
CockpitPipe *pipe;
|
||||
gulong read_sig;
|
||||
gulong close_sig;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
CockpitTransportClass parent_class;
|
||||
} CockpitInteractTransportClass;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_NAME,
|
||||
PROP_PIPE,
|
||||
PROP_BOUNDARY,
|
||||
PROP_COLOR
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (CockpitInteractTransport, cockpit_interact_transport, COCKPIT_TYPE_TRANSPORT);
|
||||
|
||||
static void
|
||||
cockpit_interact_transport_init (CockpitInteractTransport *self)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
on_pipe_read (CockpitPipe *pipe,
|
||||
GByteArray *input,
|
||||
gboolean end_of_data,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (user_data);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
guint8 *pos = NULL;
|
||||
|
||||
if (input->len > 0)
|
||||
pos = (guint8 *)g_strstr_len ((gchar *)input->data, input->len, self->delimiter);
|
||||
if (!pos)
|
||||
{
|
||||
if (!end_of_data)
|
||||
g_debug ("%s: want more data", self->name);
|
||||
break;
|
||||
}
|
||||
|
||||
guint32 size = pos - input->data;
|
||||
g_autoptr(GBytes) message = cockpit_pipe_consume (input, 0, size, self->delimiter_len);
|
||||
|
||||
g_autofree gchar *channel = NULL;
|
||||
g_autoptr(GBytes) payload = cockpit_transport_parse_frame (message, &channel);
|
||||
if (payload)
|
||||
{
|
||||
g_debug ("%s: received a %d byte payload", self->name, (int)size);
|
||||
cockpit_transport_emit_recv ((CockpitTransport *)self, channel, payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (end_of_data)
|
||||
cockpit_pipe_close (self->pipe, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_pipe_close (CockpitPipe *pipe,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (user_data);
|
||||
|
||||
g_debug ("%s: closed%s%s", self->name,
|
||||
problem ? ": " : "", problem ? problem : "");
|
||||
|
||||
cockpit_transport_emit_closed (COCKPIT_TRANSPORT (self), problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_interact_transport_constructed (GObject *object)
|
||||
{
|
||||
CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (object);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_interact_transport_parent_class)->constructed (object);
|
||||
|
||||
g_return_if_fail (self->pipe != NULL);
|
||||
g_object_get (self->pipe, "name", &self->name, NULL);
|
||||
self->read_sig = g_signal_connect (self->pipe, "read", G_CALLBACK (on_pipe_read), self);
|
||||
self->close_sig = g_signal_connect (self->pipe, "close", G_CALLBACK (on_pipe_close), self);
|
||||
|
||||
self->delimiter_len = strlen (self->delimiter);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_interact_transport_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_NAME:
|
||||
g_value_set_string (value, self->name);
|
||||
break;
|
||||
case PROP_PIPE:
|
||||
g_value_set_object (value, self->pipe);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_interact_transport_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PIPE:
|
||||
self->pipe = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_BOUNDARY:
|
||||
self->delimiter = g_strdup_printf ("\n%s\n", g_value_get_string (value));
|
||||
self->delimiter_len = strlen (self->delimiter);
|
||||
break;
|
||||
case PROP_COLOR:
|
||||
self->colored = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_interact_transport_finalize (GObject *object)
|
||||
{
|
||||
CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (object);
|
||||
|
||||
g_signal_handler_disconnect (self->pipe, self->read_sig);
|
||||
g_signal_handler_disconnect (self->pipe, self->close_sig);
|
||||
|
||||
g_free (self->name);
|
||||
g_free (self->delimiter);
|
||||
g_clear_object (&self->pipe);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_interact_transport_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_interact_transport_send (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
GBytes *payload)
|
||||
{
|
||||
CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (transport);
|
||||
GBytes *prefix;
|
||||
GBytes *suffix;
|
||||
GBytes *color;
|
||||
gchar *prefix_str;
|
||||
|
||||
if (self->colored)
|
||||
{
|
||||
color = g_bytes_new_static ("\x1b[1m", 4);
|
||||
cockpit_pipe_write (self->pipe, color);
|
||||
g_bytes_unref (color);
|
||||
}
|
||||
|
||||
prefix_str = g_strdup_printf ("%s\n", channel_id ? channel_id : "");
|
||||
prefix = g_bytes_new_take (prefix_str, strlen (prefix_str));
|
||||
cockpit_pipe_write (self->pipe, prefix);
|
||||
g_bytes_unref (prefix);
|
||||
|
||||
cockpit_pipe_write (self->pipe, payload);
|
||||
|
||||
suffix = g_bytes_new (self->delimiter, self->delimiter_len);
|
||||
cockpit_pipe_write (self->pipe, suffix);
|
||||
g_bytes_unref (suffix);
|
||||
|
||||
if (self->colored)
|
||||
{
|
||||
color = g_bytes_new_static ("\x1b[0m", 4);
|
||||
cockpit_pipe_write (self->pipe, color);
|
||||
g_bytes_unref (color);
|
||||
}
|
||||
|
||||
g_debug ("%s: queued %d byte payload", self->name, (int)g_bytes_get_size (payload));
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_interact_transport_close (CockpitTransport *transport,
|
||||
const gchar *problem)
|
||||
{
|
||||
CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (transport);
|
||||
cockpit_pipe_close (self->pipe, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_interact_transport_class_init (CockpitInteractTransportClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitTransportClass *transport_class = COCKPIT_TRANSPORT_CLASS (klass);
|
||||
|
||||
transport_class->send = cockpit_interact_transport_send;
|
||||
transport_class->close = cockpit_interact_transport_close;
|
||||
|
||||
gobject_class->constructed = cockpit_interact_transport_constructed;
|
||||
gobject_class->get_property = cockpit_interact_transport_get_property;
|
||||
gobject_class->set_property = cockpit_interact_transport_set_property;
|
||||
gobject_class->finalize = cockpit_interact_transport_finalize;
|
||||
|
||||
g_object_class_override_property (gobject_class, PROP_NAME, "name");
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_PIPE,
|
||||
g_param_spec_object ("pipe", NULL, NULL, COCKPIT_TYPE_PIPE,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_BOUNDARY,
|
||||
g_param_spec_string ("boundary", NULL, NULL, "---",
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_COLOR,
|
||||
g_param_spec_boolean ("color", NULL, NULL, FALSE,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
CockpitTransport *
|
||||
cockpit_interact_transport_new (gint in_fd,
|
||||
gint out_fd,
|
||||
const gchar *boundary)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
CockpitPipe *pipe;
|
||||
|
||||
pipe = cockpit_pipe_new ("interact", in_fd, out_fd);
|
||||
transport = g_object_new (COCKPIT_TYPE_INTERACT_TRANSPORT,
|
||||
"pipe", pipe,
|
||||
"boundary", boundary,
|
||||
"color", (gboolean)isatty (out_fd),
|
||||
NULL);
|
||||
g_object_unref (pipe);
|
||||
|
||||
return transport;
|
||||
}
|
||||
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __COCKPIT_INTERACT_TRANSPORT_H__
|
||||
#define __COCKPIT_INTERACT_TRANSPORT_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpittransport.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_INTERACT_TRANSPORT (cockpit_interact_transport_get_type ())
|
||||
#define COCKPIT_INTERACT_TRANSPORT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_INTERACT_TRANSPORT, CockpitInteractTransport))
|
||||
#define COCKPIT_IS_INTERACT_TRANSPORT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), COCKPIT_TYPE_INTERACT_TRANSPORT))
|
||||
|
||||
typedef struct _CockpitInteractTransport CockpitInteractTransport;
|
||||
|
||||
GType cockpit_interact_transport_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitTransport * cockpit_interact_transport_new (gint in_fd,
|
||||
gint out_fd,
|
||||
const gchar *boundary);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __COCKPIT_INTERACT_TRANSPORT_H__ */
|
||||
|
|
@ -1,570 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "cockpitmetrics.h"
|
||||
#include "cockpitinternalmetrics.h"
|
||||
#include "cockpitsamples.h"
|
||||
#include "cockpitcpusamples.h"
|
||||
#include "cockpitmemorysamples.h"
|
||||
#include "cockpitblocksamples.h"
|
||||
#include "cockpitnetworksamples.h"
|
||||
#include "cockpitmountsamples.h"
|
||||
#include "cockpitcgroupsamples.h"
|
||||
#include "cockpitdisksamples.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
/**
|
||||
* CockpitInternalMetrics:
|
||||
*
|
||||
* A #CockpitMetrics channel that pulls data from internal sources
|
||||
*/
|
||||
|
||||
static void cockpit_samples_interface_init (CockpitSamplesInterface *iface);
|
||||
|
||||
#define COCKPIT_INTERNAL_METRICS(o) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_INTERNAL_METRICS, CockpitInternalMetrics))
|
||||
|
||||
typedef enum {
|
||||
CPU_SAMPLER = 1 << 0,
|
||||
MEMORY_SAMPLER = 1 << 1,
|
||||
BLOCK_SAMPLER = 1 << 2,
|
||||
NETWORK_SAMPLER = 1 << 3,
|
||||
MOUNT_SAMPLER = 1 << 4,
|
||||
CGROUP_SAMPLER = 1 << 5,
|
||||
DISK_SAMPLER = 1 << 6,
|
||||
THERMAL_SAMPLER = 1 << 7,
|
||||
CGROUP_IO_SAMPLER = 1 << 8,
|
||||
} SamplerSet;
|
||||
|
||||
typedef struct {
|
||||
const gchar *name;
|
||||
const gchar *units;
|
||||
const gchar *semantics;
|
||||
gboolean instanced;
|
||||
SamplerSet sampler;
|
||||
} MetricDescription;
|
||||
|
||||
static MetricDescription metric_descriptions[] = {
|
||||
{ "cpu.basic.nice", "millisec", "counter", FALSE, CPU_SAMPLER },
|
||||
{ "cpu.basic.user", "millisec", "counter", FALSE, CPU_SAMPLER },
|
||||
{ "cpu.basic.system", "millisec", "counter", FALSE, CPU_SAMPLER },
|
||||
{ "cpu.basic.iowait", "millisec", "counter", FALSE, CPU_SAMPLER },
|
||||
{ "cpu.core.nice", "millisec", "counter", TRUE, CPU_SAMPLER },
|
||||
{ "cpu.core.user", "millisec", "counter", TRUE, CPU_SAMPLER },
|
||||
{ "cpu.core.system", "millisec", "counter", TRUE, CPU_SAMPLER },
|
||||
{ "cpu.core.iowait", "millisec", "counter", TRUE, CPU_SAMPLER },
|
||||
|
||||
{ "memory.free", "bytes", "instant", FALSE, MEMORY_SAMPLER },
|
||||
{ "memory.used", "bytes", "instant", FALSE, MEMORY_SAMPLER },
|
||||
{ "memory.cached", "bytes", "instant", FALSE, MEMORY_SAMPLER },
|
||||
{ "memory.swap-used", "bytes", "instant", FALSE, MEMORY_SAMPLER },
|
||||
|
||||
{ "block.device.read", "bytes", "counter", TRUE, BLOCK_SAMPLER },
|
||||
{ "block.device.written", "bytes", "counter", TRUE, BLOCK_SAMPLER },
|
||||
|
||||
{ "disk.all.read", "bytes", "counter", FALSE, DISK_SAMPLER },
|
||||
{ "disk.all.written", "bytes", "counter", FALSE, DISK_SAMPLER },
|
||||
{ "disk.dev.read", "bytes", "counter", TRUE, DISK_SAMPLER },
|
||||
{ "disk.dev.written", "bytes", "counter", TRUE, DISK_SAMPLER },
|
||||
|
||||
{ "network.all.rx", "bytes", "counter", FALSE, NETWORK_SAMPLER }, /* deprecated */
|
||||
{ "network.all.tx", "bytes", "counter", FALSE, NETWORK_SAMPLER }, /* deprecated */
|
||||
{ "network.interface.rx", "bytes", "counter", TRUE, NETWORK_SAMPLER },
|
||||
{ "network.interface.tx", "bytes", "counter", TRUE, NETWORK_SAMPLER },
|
||||
|
||||
{ "mount.total", "bytes", "instant", TRUE, MOUNT_SAMPLER },
|
||||
{ "mount.used", "bytes", "instant", TRUE, MOUNT_SAMPLER },
|
||||
|
||||
{ "cgroup.memory.usage", "bytes", "instant", TRUE, CGROUP_SAMPLER },
|
||||
{ "cgroup.memory.limit", "bytes", "instant", TRUE, CGROUP_SAMPLER },
|
||||
{ "cgroup.memory.sw-usage", "bytes", "instant", TRUE, CGROUP_SAMPLER },
|
||||
{ "cgroup.memory.sw-limit", "bytes", "instant", TRUE, CGROUP_SAMPLER },
|
||||
{ "cgroup.cpu.usage", "millisec", "counter", TRUE, CGROUP_SAMPLER },
|
||||
{ "cgroup.cpu.shares", "count", "instant", TRUE, CGROUP_SAMPLER },
|
||||
|
||||
{ "cpu.temperature", "celsius", "instant", TRUE, THERMAL_SAMPLER },
|
||||
|
||||
{ "disk.cgroup.read", "bytes", "counter", TRUE, CGROUP_IO_SAMPLER },
|
||||
{ "disk.cgroup.written", "bytes", "counter", TRUE, CGROUP_IO_SAMPLER },
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static MetricDescription *
|
||||
find_metric_description (const gchar *name)
|
||||
{
|
||||
for (MetricDescription *d = metric_descriptions; d->name; d++)
|
||||
{
|
||||
if (g_strcmp0 (d->name, name) == 0)
|
||||
return d;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
gboolean seen;
|
||||
int index;
|
||||
double value;
|
||||
} InstanceInfo;
|
||||
|
||||
typedef struct {
|
||||
MetricDescription *desc;
|
||||
const gchar *derive;
|
||||
|
||||
GHashTable *instances;
|
||||
double value;
|
||||
} MetricInfo;
|
||||
|
||||
typedef struct {
|
||||
CockpitMetrics parent;
|
||||
const gchar *name;
|
||||
|
||||
gint64 interval;
|
||||
int n_metrics;
|
||||
MetricInfo *metrics;
|
||||
const gchar **omit_instances;
|
||||
SamplerSet samplers;
|
||||
|
||||
gboolean need_meta;
|
||||
} CockpitInternalMetrics;
|
||||
|
||||
typedef struct {
|
||||
CockpitMetricsClass parent_class;
|
||||
} CockpitInternalMetricsClass;
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CockpitInternalMetrics, cockpit_internal_metrics, COCKPIT_TYPE_METRICS,
|
||||
G_IMPLEMENT_INTERFACE (COCKPIT_TYPE_SAMPLES,
|
||||
cockpit_samples_interface_init))
|
||||
|
||||
static void
|
||||
cockpit_internal_metrics_init (CockpitInternalMetrics *self)
|
||||
{
|
||||
}
|
||||
|
||||
static gint64
|
||||
timestamp_from_timeval (struct timeval *tv)
|
||||
{
|
||||
return tv->tv_sec * 1000 + tv->tv_usec / 1000;
|
||||
}
|
||||
|
||||
static void
|
||||
send_meta (CockpitInternalMetrics *self)
|
||||
{
|
||||
JsonArray *metrics;
|
||||
JsonObject *metric;
|
||||
JsonObject *root;
|
||||
struct timeval now_timeval;
|
||||
gint64 now;
|
||||
|
||||
gettimeofday (&now_timeval, NULL);
|
||||
now = timestamp_from_timeval (&now_timeval);
|
||||
|
||||
root = json_object_new ();
|
||||
json_object_set_int_member (root, "timestamp", now);
|
||||
json_object_set_int_member (root, "now", now);
|
||||
json_object_set_int_member (root, "interval", self->interval);
|
||||
|
||||
metrics = json_array_new ();
|
||||
for (int i = 0; i < self->n_metrics; i++)
|
||||
{
|
||||
MetricInfo *info = &self->metrics[i];
|
||||
metric = json_object_new ();
|
||||
|
||||
/* Name and derivation mode
|
||||
*/
|
||||
json_object_set_string_member (metric, "name", info->desc->name);
|
||||
if (info->derive)
|
||||
json_object_set_string_member (metric, "derive", info->derive);
|
||||
|
||||
/* Instances
|
||||
*/
|
||||
if (info->desc->instanced)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
int index;
|
||||
JsonArray *instances = json_array_new ();
|
||||
|
||||
g_hash_table_iter_init (&iter, info->instances);
|
||||
index = 0;
|
||||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||||
{
|
||||
const gchar *name = key;
|
||||
InstanceInfo *inst = value;
|
||||
|
||||
/* HACK: We can't use json_builder_add_string_value here since
|
||||
it turns empty strings into 'null' values inside arrays.
|
||||
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=730803
|
||||
*/
|
||||
{
|
||||
JsonNode *string_element = json_node_alloc ();
|
||||
json_node_init_string (string_element, name);
|
||||
json_array_add_element (instances, string_element);
|
||||
}
|
||||
|
||||
inst->index = index++;
|
||||
}
|
||||
json_object_set_array_member (metric, "instances", instances);
|
||||
}
|
||||
|
||||
/* Units and semantics
|
||||
*/
|
||||
json_object_set_string_member (metric, "units", info->desc->units);
|
||||
json_object_set_string_member (metric, "semantics", info->desc->semantics);
|
||||
|
||||
json_array_add_object_element (metrics, metric);
|
||||
}
|
||||
|
||||
json_object_set_array_member (root, "metrics", metrics);
|
||||
|
||||
cockpit_metrics_send_meta (COCKPIT_METRICS (self), root, FALSE);
|
||||
|
||||
json_object_unref (root);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_internal_metrics_sample (CockpitSamples *samples,
|
||||
const gchar *metric,
|
||||
const gchar *instance,
|
||||
gint64 value)
|
||||
{
|
||||
CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (samples);
|
||||
|
||||
if (self->omit_instances)
|
||||
{
|
||||
for (int i = 0; self->omit_instances[i]; i++)
|
||||
{
|
||||
if (g_strcmp0 (instance, self->omit_instances[i]) == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < self->n_metrics; i++)
|
||||
{
|
||||
MetricInfo *info = &self->metrics[i];
|
||||
if (g_strcmp0 (metric, info->desc->name) != 0)
|
||||
continue;
|
||||
|
||||
if (info->desc->instanced)
|
||||
{
|
||||
InstanceInfo *inst = g_hash_table_lookup (info->instances, instance);
|
||||
if (inst == NULL)
|
||||
{
|
||||
g_debug ("%s + %s", metric, instance);
|
||||
inst = g_new0 (InstanceInfo, 1);
|
||||
g_hash_table_insert (info->instances, g_strdup (instance), inst);
|
||||
self->need_meta = TRUE;
|
||||
}
|
||||
inst->seen = TRUE;
|
||||
inst->value = value;
|
||||
}
|
||||
else
|
||||
info->value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
instance_reset (gpointer key,
|
||||
gpointer value,
|
||||
gpointer user_data)
|
||||
{
|
||||
InstanceInfo *info = value;
|
||||
info->seen = FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
instance_unseen (gpointer key,
|
||||
gpointer value,
|
||||
gpointer user_data)
|
||||
{
|
||||
InstanceInfo *info = value;
|
||||
return !info->seen;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_internal_metrics_tick (CockpitMetrics *metrics,
|
||||
gint64 timestamp)
|
||||
{
|
||||
CockpitInternalMetrics *self = (CockpitInternalMetrics *)metrics;
|
||||
struct timeval now_timeval;
|
||||
gint64 now;
|
||||
|
||||
gettimeofday (&now_timeval, NULL);
|
||||
now = timestamp_from_timeval (&now_timeval);
|
||||
|
||||
/* Reset samples
|
||||
*/
|
||||
for (int i = 0; i < self->n_metrics; i++)
|
||||
{
|
||||
MetricInfo *info = &self->metrics[i];
|
||||
if (info->desc->instanced)
|
||||
g_hash_table_foreach (info->instances, instance_reset, NULL);
|
||||
else
|
||||
info->value = NAN;
|
||||
}
|
||||
|
||||
/* Sample
|
||||
*/
|
||||
if (self->samplers & CPU_SAMPLER)
|
||||
cockpit_cpu_samples (COCKPIT_SAMPLES (self));
|
||||
if (self->samplers & MEMORY_SAMPLER)
|
||||
cockpit_memory_samples (COCKPIT_SAMPLES (self));
|
||||
if (self->samplers & BLOCK_SAMPLER)
|
||||
cockpit_block_samples (COCKPIT_SAMPLES (self));
|
||||
if (self->samplers & NETWORK_SAMPLER)
|
||||
cockpit_network_samples (COCKPIT_SAMPLES (self));
|
||||
if (self->samplers & MOUNT_SAMPLER)
|
||||
cockpit_mount_samples (COCKPIT_SAMPLES (self));
|
||||
if (self->samplers & CGROUP_SAMPLER)
|
||||
cockpit_cgroup_samples (COCKPIT_SAMPLES (self));
|
||||
if (self->samplers & DISK_SAMPLER)
|
||||
cockpit_disk_samples (COCKPIT_SAMPLES (self));
|
||||
if (self->samplers & THERMAL_SAMPLER)
|
||||
cockpit_cpu_temperature (COCKPIT_SAMPLES (self));
|
||||
if (self->samplers & CGROUP_IO_SAMPLER)
|
||||
cockpit_cgroup_disk_usage (COCKPIT_SAMPLES (self));
|
||||
|
||||
/* Check for disappeared instances
|
||||
*/
|
||||
for (int i = 0; i < self->n_metrics; i++)
|
||||
{
|
||||
MetricInfo *info = &self->metrics[i];
|
||||
if (info->desc->instanced)
|
||||
if (g_hash_table_foreach_remove (info->instances, instance_unseen, NULL) > 0)
|
||||
self->need_meta = TRUE;
|
||||
}
|
||||
|
||||
/* Send a meta message if necessary. This will also allocate a new
|
||||
buffer and setup the instance indices.
|
||||
*/
|
||||
if (self->need_meta)
|
||||
{
|
||||
send_meta (self);
|
||||
self->need_meta = FALSE;
|
||||
}
|
||||
|
||||
/* Ship them out
|
||||
*/
|
||||
double **buffer = cockpit_metrics_get_data_buffer (COCKPIT_METRICS (self));
|
||||
for (int i = 0; i < self->n_metrics; i++)
|
||||
{
|
||||
MetricInfo *info = &self->metrics[i];
|
||||
if (info->desc->instanced)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
|
||||
g_hash_table_iter_init (&iter, info->instances);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||||
{
|
||||
InstanceInfo *inst = value;
|
||||
buffer[i][inst->index] = inst->value;
|
||||
}
|
||||
}
|
||||
else
|
||||
buffer[i][0] = info->value;
|
||||
}
|
||||
|
||||
cockpit_metrics_send_data (COCKPIT_METRICS (self), now);
|
||||
cockpit_metrics_flush_data (COCKPIT_METRICS (self));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
convert_metric_description (CockpitInternalMetrics *self,
|
||||
JsonNode *node,
|
||||
MetricInfo *info,
|
||||
int index)
|
||||
{
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (self);
|
||||
const gchar *name;
|
||||
const gchar *units;
|
||||
|
||||
if (json_node_get_node_type (node) == JSON_NODE_OBJECT)
|
||||
{
|
||||
if (!cockpit_json_get_string (json_node_get_object (node), "name", NULL, &name)
|
||||
|| name == NULL)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"metrics\" option was specified (no name for metric %d)", index);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_string (json_node_get_object (node), "units", NULL, &units))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid units for metric %s (not a string)", name);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_string (json_node_get_object (node), "derive", NULL, &info->derive))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid derivation mode for metric %s (not a string)", name);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"metrics\" option was specified (not an object for metric %d)", index);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
MetricDescription *desc = find_metric_description (name);
|
||||
if (desc == NULL)
|
||||
{
|
||||
g_message ("unknown internal metric %s", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (units && g_strcmp0 (desc->units, units) != 0)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"%s has units %s, not %s", name, desc->units, units);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (desc->instanced)
|
||||
info->instances = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
|
||||
info->desc = desc;
|
||||
self->samplers |= desc->sampler;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_internal_metrics_prepare (CockpitChannel *channel)
|
||||
{
|
||||
CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (channel);
|
||||
JsonObject *options;
|
||||
JsonArray *metrics;
|
||||
int i;
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_internal_metrics_parent_class)->prepare (channel);
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
|
||||
/* "omit-instances" option */
|
||||
if (!cockpit_json_get_strv (options, "omit-instances", NULL, &self->omit_instances))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"omit-instances\" option (not an array of strings)");
|
||||
return;
|
||||
}
|
||||
|
||||
/* "metrics" option */
|
||||
self->n_metrics = 0;
|
||||
if (!cockpit_json_get_array (options, "metrics", NULL, &metrics))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"metrics\" option was specified (not an array)");
|
||||
return;
|
||||
}
|
||||
if (metrics)
|
||||
self->n_metrics = json_array_get_length (metrics);
|
||||
|
||||
self->metrics = g_new0 (MetricInfo, self->n_metrics);
|
||||
for (i = 0; i < self->n_metrics; i++)
|
||||
{
|
||||
MetricInfo *info = &self->metrics[i];
|
||||
if (!convert_metric_description (self, json_array_get_element (metrics, i), info, i))
|
||||
return;
|
||||
if (!info->desc)
|
||||
{
|
||||
cockpit_channel_close (channel, "not-supported");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* "interval" option */
|
||||
if (!cockpit_json_get_int (options, "interval", 1000, &self->interval))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"interval\" option");
|
||||
return;
|
||||
}
|
||||
else if (self->interval <= 0 || self->interval > G_MAXINT)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"interval\" value: %" G_GINT64_FORMAT, self->interval);
|
||||
return;
|
||||
}
|
||||
|
||||
self->need_meta = TRUE;
|
||||
|
||||
cockpit_metrics_metronome (COCKPIT_METRICS (self), self->interval);
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_internal_metrics_dispose (GObject *object)
|
||||
{
|
||||
#if 0
|
||||
CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (object);
|
||||
#endif
|
||||
G_OBJECT_CLASS (cockpit_internal_metrics_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_internal_metrics_finalize (GObject *object)
|
||||
{
|
||||
CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (object);
|
||||
|
||||
g_free (self->omit_instances);
|
||||
|
||||
for (int i = 0; i < self->n_metrics; i++)
|
||||
{
|
||||
MetricInfo *info = &self->metrics[i];
|
||||
if (info->instances)
|
||||
g_hash_table_unref (info->instances);
|
||||
}
|
||||
|
||||
g_free (self->metrics);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_internal_metrics_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_internal_metrics_class_init (CockpitInternalMetricsClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitMetricsClass *metrics_class = COCKPIT_METRICS_CLASS (klass);
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = cockpit_internal_metrics_dispose;
|
||||
gobject_class->finalize = cockpit_internal_metrics_finalize;
|
||||
|
||||
channel_class->prepare = cockpit_internal_metrics_prepare;
|
||||
metrics_class->tick = cockpit_internal_metrics_tick;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_samples_interface_init (CockpitSamplesInterface *iface)
|
||||
{
|
||||
iface->sample = cockpit_internal_metrics_sample;
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_INTERNAL_METRICS_H__
|
||||
#define COCKPIT_INTERNAL_METRICS_H__
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_INTERNAL_METRICS (cockpit_internal_metrics_get_type ())
|
||||
|
||||
GType cockpit_internal_metrics_get_type (void) G_GNUC_CONST;
|
||||
|
||||
#endif /* COCKPIT_INTERNAL_METRICS_H__ */
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitmemorysamples.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* TODO: this should be optimized so we don't allocate memory and call open()/close() all the time */
|
||||
|
||||
void
|
||||
cockpit_memory_samples (CockpitSamples *samples)
|
||||
{
|
||||
gchar *contents = NULL;
|
||||
GError *error = NULL;
|
||||
gchar **lines = NULL;
|
||||
gsize len;
|
||||
guint n;
|
||||
|
||||
guint64 free_kb = 0;
|
||||
guint64 total_kb = 0;
|
||||
guint64 buffers_kb = 0;
|
||||
guint64 cached_kb = 0;
|
||||
guint64 available_kb = 0;
|
||||
guint64 swap_total_kb = 0;
|
||||
guint64 swap_free_kb = 0;
|
||||
|
||||
if (!g_file_get_contents ("/proc/meminfo", &contents, &len, &error))
|
||||
{
|
||||
g_message ("error loading contents /proc/meminfo: %s", error->message);
|
||||
g_error_free (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* see 'man proc' for the format of /proc/stat */
|
||||
|
||||
lines = g_strsplit (contents, "\n", -1);
|
||||
for (n = 0; lines != NULL && lines[n] != NULL; n++)
|
||||
{
|
||||
const gchar *line = lines[n];
|
||||
if (g_str_has_prefix (line, "MemTotal:"))
|
||||
g_warn_if_fail (sscanf (line + sizeof ("MemTotal:") - 1, "%" G_GUINT64_FORMAT, &total_kb) == 1);
|
||||
else if (g_str_has_prefix (line, "MemFree:"))
|
||||
g_warn_if_fail (sscanf (line + sizeof ("MemFree:") - 1, "%" G_GUINT64_FORMAT, &free_kb) == 1);
|
||||
else if (g_str_has_prefix (line, "SwapTotal:"))
|
||||
g_warn_if_fail (sscanf (line + sizeof ("SwapTotal:") - 1, "%" G_GUINT64_FORMAT, &swap_total_kb) == 1);
|
||||
else if (g_str_has_prefix (line, "SwapFree:"))
|
||||
g_warn_if_fail (sscanf (line + sizeof ("SwapFree:") - 1, "%" G_GUINT64_FORMAT, &swap_free_kb) == 1);
|
||||
else if (g_str_has_prefix (line, "Buffers:"))
|
||||
g_warn_if_fail (sscanf (line + sizeof ("Buffers:") - 1, "%" G_GUINT64_FORMAT, &buffers_kb) == 1);
|
||||
else if (g_str_has_prefix (line, "Cached:"))
|
||||
g_warn_if_fail (sscanf (line + sizeof ("Cached:") - 1, "%" G_GUINT64_FORMAT, &cached_kb) == 1);
|
||||
else if (g_str_has_prefix (line, "MemAvailable:"))
|
||||
g_warn_if_fail (sscanf (line + sizeof ("MemAvailable:") - 1, "%" G_GUINT64_FORMAT, &available_kb) == 1);
|
||||
|
||||
}
|
||||
|
||||
cockpit_samples_sample (samples, "memory.free", NULL, free_kb * 1024);
|
||||
cockpit_samples_sample (samples, "memory.used", NULL, (total_kb - available_kb) * 1024);
|
||||
cockpit_samples_sample (samples, "memory.cached", NULL, (buffers_kb + cached_kb) * 1024);
|
||||
cockpit_samples_sample (samples, "memory.swap-used", NULL, (swap_total_kb - swap_free_kb) * 1024);
|
||||
|
||||
out:
|
||||
g_strfreev (lines);
|
||||
g_free (contents);
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_MEMORY_SAMPLES_H__
|
||||
#define COCKPIT_MEMORY_SAMPLES_H__
|
||||
|
||||
#include "cockpitsamples.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void cockpit_memory_samples (CockpitSamples *samples);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_MEMORY_SAMPLES_H__ */
|
||||
|
|
@ -1,613 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitmetrics.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
enum {
|
||||
DERIVE_NONE = 0,
|
||||
DERIVE_DELTA = 1,
|
||||
DERIVE_RATE = 2,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
gint derive;
|
||||
gboolean has_instances;
|
||||
gint n_last_instances;
|
||||
gint n_next_instances;
|
||||
} MetricInfo;
|
||||
|
||||
typedef struct {
|
||||
gboolean interpolate;
|
||||
gboolean compress;
|
||||
|
||||
guint timeout;
|
||||
gint64 next;
|
||||
gint64 interval;
|
||||
|
||||
gint64 meta_interval;
|
||||
gboolean meta_reset;
|
||||
JsonObject *last_meta;
|
||||
JsonObject *next_meta;
|
||||
|
||||
gint n_metrics;
|
||||
MetricInfo *metric_info;
|
||||
gint64 last_timestamp;
|
||||
gint64 next_timestamp;
|
||||
double **last_data;
|
||||
double **next_data;
|
||||
gboolean derived_valid;
|
||||
double **derived;
|
||||
|
||||
JsonArray *message;
|
||||
} CockpitMetricsPrivate;
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (CockpitMetrics, cockpit_metrics, COCKPIT_TYPE_CHANNEL,
|
||||
G_ADD_PRIVATE (CockpitMetrics));
|
||||
|
||||
#define GET_PRIV(self) ((CockpitMetricsPrivate *) cockpit_metrics_get_instance_private (self))
|
||||
|
||||
static void
|
||||
cockpit_metrics_init (CockpitMetrics *self)
|
||||
{
|
||||
GET_PRIV(self)->interpolate = TRUE;
|
||||
GET_PRIV(self)->compress = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_metrics_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "received unexpected metrics1 payload");
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_metrics_close (CockpitChannel *channel,
|
||||
const gchar *problem)
|
||||
{
|
||||
CockpitMetrics *self = COCKPIT_METRICS (channel);
|
||||
|
||||
if (GET_PRIV(self)->timeout)
|
||||
{
|
||||
g_source_remove (GET_PRIV(self)->timeout);
|
||||
GET_PRIV(self)->timeout = 0;
|
||||
}
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_metrics_parent_class)->close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_metrics_dispose (GObject *object)
|
||||
{
|
||||
CockpitMetrics *self = COCKPIT_METRICS (object);
|
||||
|
||||
if (GET_PRIV(self)->timeout)
|
||||
{
|
||||
g_source_remove (GET_PRIV(self)->timeout);
|
||||
GET_PRIV(self)->timeout = 0;
|
||||
}
|
||||
|
||||
if (GET_PRIV(self)->last_meta)
|
||||
{
|
||||
json_object_unref (GET_PRIV(self)->last_meta);
|
||||
GET_PRIV(self)->last_meta = NULL;
|
||||
}
|
||||
|
||||
if (GET_PRIV(self)->last_data)
|
||||
{
|
||||
g_free (GET_PRIV(self)->last_data[0]);
|
||||
g_free (GET_PRIV(self)->last_data);
|
||||
GET_PRIV(self)->last_data = NULL;
|
||||
}
|
||||
|
||||
if (GET_PRIV(self)->next_meta)
|
||||
{
|
||||
json_object_unref (GET_PRIV(self)->next_meta);
|
||||
GET_PRIV(self)->next_meta = NULL;
|
||||
}
|
||||
|
||||
if (GET_PRIV(self)->next_data)
|
||||
{
|
||||
g_free (GET_PRIV(self)->next_data[0]);
|
||||
g_free (GET_PRIV(self)->next_data);
|
||||
GET_PRIV(self)->next_data = NULL;
|
||||
}
|
||||
|
||||
if (GET_PRIV(self)->derived)
|
||||
{
|
||||
g_free (GET_PRIV(self)->derived[0]);
|
||||
g_free (GET_PRIV(self)->derived);
|
||||
GET_PRIV(self)->derived = NULL;
|
||||
}
|
||||
|
||||
g_free (GET_PRIV(self)->metric_info);
|
||||
GET_PRIV(self)->metric_info = NULL;
|
||||
|
||||
G_OBJECT_CLASS (cockpit_metrics_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_metrics_class_init (CockpitMetricsClass *klass)
|
||||
{
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = cockpit_metrics_dispose;
|
||||
|
||||
channel_class->recv = cockpit_metrics_recv;
|
||||
channel_class->close = cockpit_metrics_close;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_timeout_tick (gpointer data)
|
||||
{
|
||||
CockpitMetrics *self = data;
|
||||
CockpitMetricsClass *klass;
|
||||
gint64 next_interval;
|
||||
|
||||
if (GET_PRIV(self)->timeout > 0)
|
||||
{
|
||||
g_source_remove (GET_PRIV(self)->timeout);
|
||||
GET_PRIV(self)->timeout = 0;
|
||||
}
|
||||
|
||||
klass = COCKPIT_METRICS_GET_CLASS (self);
|
||||
if (klass->tick)
|
||||
(klass->tick) (self, GET_PRIV(self)->next);
|
||||
|
||||
GET_PRIV(self)->next += GET_PRIV(self)->interval;
|
||||
next_interval = GET_PRIV(self)->next - g_get_monotonic_time() / 1000;
|
||||
if (next_interval < 0)
|
||||
next_interval = 0;
|
||||
|
||||
if (next_interval <= G_MAXUINT)
|
||||
GET_PRIV(self)->timeout = g_timeout_add (next_interval, on_timeout_tick, self);
|
||||
else if (next_interval / 1000 <= G_MAXUINT)
|
||||
GET_PRIV(self)->timeout = g_timeout_add_seconds (next_interval / 1000, on_timeout_tick, self);
|
||||
else
|
||||
{
|
||||
cockpit_channel_fail (COCKPIT_CHANNEL (self), "internal-error",
|
||||
"invalid metric timeout tick offset");
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_metrics_metronome (CockpitMetrics *self,
|
||||
gint64 interval)
|
||||
{
|
||||
g_return_if_fail (GET_PRIV(self)->timeout == 0);
|
||||
g_return_if_fail (interval > 0);
|
||||
|
||||
GET_PRIV(self)->next = g_get_monotonic_time() / 1000;
|
||||
GET_PRIV(self)->interval = interval;
|
||||
on_timeout_tick (self);
|
||||
}
|
||||
|
||||
static void
|
||||
realloc_next_buffer (CockpitMetrics *self)
|
||||
{
|
||||
int total_next_instances = 0;
|
||||
for (int i = 0; i < GET_PRIV(self)->n_metrics; i++)
|
||||
total_next_instances += GET_PRIV(self)->metric_info[i].n_next_instances;
|
||||
g_free (GET_PRIV(self)->next_data[0]);
|
||||
GET_PRIV(self)->next_data[0] = g_new (double, total_next_instances);
|
||||
for (int i = 1; i < GET_PRIV(self)->n_metrics; i++)
|
||||
GET_PRIV(self)->next_data[i] = GET_PRIV(self)->next_data[i-1] + GET_PRIV(self)->metric_info[i-1].n_next_instances;
|
||||
}
|
||||
|
||||
static void
|
||||
realloc_derived_buffer (CockpitMetrics *self)
|
||||
{
|
||||
int total_next_instances = 0;
|
||||
for (int i = 0; i < GET_PRIV(self)->n_metrics; i++)
|
||||
total_next_instances += GET_PRIV(self)->metric_info[i].n_next_instances;
|
||||
g_free (GET_PRIV(self)->derived[0]);
|
||||
GET_PRIV(self)->derived[0] = g_new (double, total_next_instances);
|
||||
for (int i = 1; i < GET_PRIV(self)->n_metrics; i++)
|
||||
GET_PRIV(self)->derived[i] = GET_PRIV(self)->derived[i-1] + GET_PRIV(self)->metric_info[i-1].n_next_instances;
|
||||
GET_PRIV(self)->derived_valid = FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
update_for_meta (CockpitMetrics *self,
|
||||
JsonObject *meta,
|
||||
gboolean reset)
|
||||
{
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (self);
|
||||
JsonArray *array;
|
||||
JsonObject *info;
|
||||
JsonArray *instances;
|
||||
guint length;
|
||||
gchar const *derive;
|
||||
|
||||
array = json_object_get_array_member (meta, "metrics");
|
||||
g_return_val_if_fail (array != NULL, FALSE);
|
||||
length = json_array_get_length (array);
|
||||
|
||||
if (GET_PRIV(self)->metric_info == NULL)
|
||||
{
|
||||
GET_PRIV(self)->n_metrics = length;
|
||||
GET_PRIV(self)->metric_info = g_new0 (MetricInfo, length);
|
||||
GET_PRIV(self)->last_data = g_new0 (double *, length);
|
||||
GET_PRIV(self)->next_data = g_new0 (double *, length);
|
||||
GET_PRIV(self)->derived = g_new0 (double *, length);
|
||||
|
||||
reset = TRUE;
|
||||
}
|
||||
else if (GET_PRIV(self)->n_metrics != length)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"number of metrics must not change");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
info = json_array_get_object_element (array, i);
|
||||
g_return_val_if_fail (info != NULL, FALSE);
|
||||
|
||||
if (!cockpit_json_get_string (info, "derive", NULL, &derive))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"unsupported derive value: not a string");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!derive)
|
||||
{
|
||||
GET_PRIV(self)->metric_info[i].derive = DERIVE_NONE;
|
||||
}
|
||||
else if (g_str_equal (derive, "delta"))
|
||||
{
|
||||
GET_PRIV(self)->metric_info[i].derive = DERIVE_DELTA;
|
||||
}
|
||||
else if (g_str_equal (derive, "rate"))
|
||||
{
|
||||
GET_PRIV(self)->metric_info[i].derive = DERIVE_RATE;
|
||||
}
|
||||
else
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"unsupported derive function: %s", derive);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_array (info, "instances", NULL, &instances))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"unsupported instances value: not a string");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (instances)
|
||||
{
|
||||
GET_PRIV(self)->metric_info[i].has_instances = TRUE;
|
||||
GET_PRIV(self)->metric_info[i].n_next_instances = json_array_get_length (instances);
|
||||
}
|
||||
else
|
||||
{
|
||||
GET_PRIV(self)->metric_info[i].has_instances = FALSE;
|
||||
GET_PRIV(self)->metric_info[i].n_next_instances = 1;
|
||||
}
|
||||
}
|
||||
|
||||
realloc_next_buffer (self);
|
||||
realloc_derived_buffer (self);
|
||||
|
||||
g_return_val_if_fail (cockpit_json_get_int (meta, "interval", 1000, &GET_PRIV(self)->meta_interval),
|
||||
FALSE);
|
||||
|
||||
GET_PRIV(self)->meta_reset = reset;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
send_object (CockpitMetrics *self,
|
||||
JsonObject *object)
|
||||
{
|
||||
CockpitChannel *channel = (CockpitChannel *)self;
|
||||
GBytes *bytes;
|
||||
|
||||
bytes = cockpit_json_write_bytes (object);
|
||||
cockpit_channel_send (channel, bytes, TRUE);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
/*
|
||||
* cockpit_metrics_send_meta:
|
||||
* @self: The CockpitMetrics
|
||||
* @meta: An object containing metric meta data
|
||||
*
|
||||
* Send metrics meta data down the channel. If you use cockpit_metrics_send_data()
|
||||
* then you must use this function instead of sending stuff on the channel directly.
|
||||
*/
|
||||
void
|
||||
cockpit_metrics_send_meta (CockpitMetrics *self,
|
||||
JsonObject *meta,
|
||||
gboolean reset)
|
||||
{
|
||||
cockpit_metrics_flush_data (self);
|
||||
|
||||
if (GET_PRIV(self)->next_meta)
|
||||
json_object_unref (GET_PRIV(self)->next_meta);
|
||||
GET_PRIV(self)->next_meta = json_object_ref (meta);
|
||||
|
||||
if (update_for_meta (self, meta, reset))
|
||||
send_object (self, meta);
|
||||
}
|
||||
|
||||
static void
|
||||
send_array (CockpitMetrics *self,
|
||||
JsonArray *array)
|
||||
{
|
||||
CockpitChannel *channel = (CockpitChannel *)self;
|
||||
GBytes *bytes;
|
||||
JsonNode *node;
|
||||
gsize length;
|
||||
gchar *ret;
|
||||
|
||||
node = json_node_new (JSON_NODE_ARRAY);
|
||||
json_node_set_array (node, array);
|
||||
ret = cockpit_json_write (node, &length);
|
||||
json_node_free (node);
|
||||
|
||||
bytes = g_bytes_new_take (ret, length);
|
||||
cockpit_channel_send (channel, bytes, TRUE);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
static JsonArray *
|
||||
push_array_at (JsonArray *array,
|
||||
guint index,
|
||||
JsonNode *node)
|
||||
{
|
||||
if (array == NULL)
|
||||
array = json_array_new ();
|
||||
|
||||
g_assert (index >= json_array_get_length (array));
|
||||
|
||||
while (index > json_array_get_length (array))
|
||||
json_array_add_null_element (array);
|
||||
|
||||
if (node)
|
||||
json_array_add_element (array, node);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
static JsonArray *
|
||||
compute_and_maybe_push_value (CockpitMetrics *self,
|
||||
double interpol_r,
|
||||
int metric,
|
||||
int next_instance,
|
||||
int last_instance,
|
||||
JsonArray *array,
|
||||
int index)
|
||||
{
|
||||
double val = GET_PRIV(self)->next_data[metric][next_instance];
|
||||
|
||||
if (last_instance >= 0)
|
||||
{
|
||||
double last_val = GET_PRIV(self)->last_data[metric][last_instance];
|
||||
|
||||
if (GET_PRIV(self)->interpolate && !isnan (last_val))
|
||||
{
|
||||
val = last_val * (1.0 - interpol_r) + val * interpol_r;
|
||||
GET_PRIV(self)->next_data[metric][next_instance] = val;
|
||||
}
|
||||
|
||||
switch (GET_PRIV(self)->metric_info[metric].derive)
|
||||
{
|
||||
case DERIVE_DELTA:
|
||||
val = val - last_val;
|
||||
break;
|
||||
case DERIVE_RATE:
|
||||
val = (val - last_val) / (GET_PRIV(self)->next_timestamp - GET_PRIV(self)->last_timestamp) * 1000;
|
||||
break;
|
||||
case DERIVE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GET_PRIV(self)->metric_info[metric].derive != DERIVE_NONE)
|
||||
val = NAN;
|
||||
}
|
||||
|
||||
if (GET_PRIV(self)->compress == FALSE
|
||||
|| next_instance != last_instance
|
||||
|| !GET_PRIV(self)->derived_valid
|
||||
|| val != GET_PRIV(self)->derived[metric][next_instance])
|
||||
{
|
||||
GET_PRIV(self)->derived[metric][next_instance] = val;
|
||||
|
||||
JsonNode *node = json_node_new (JSON_NODE_VALUE);
|
||||
if (!isnan (val))
|
||||
json_node_set_double (node, val);
|
||||
else
|
||||
json_node_set_boolean (node, FALSE);
|
||||
array = push_array_at (array, index, node);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
static int
|
||||
find_last_instance (CockpitMetrics *self,
|
||||
int metric,
|
||||
int instance)
|
||||
{
|
||||
if (GET_PRIV(self)->meta_reset)
|
||||
return -1;
|
||||
|
||||
if (GET_PRIV(self)->last_meta == GET_PRIV(self)->next_meta)
|
||||
return instance;
|
||||
|
||||
JsonArray *last_metrics = json_object_get_array_member (GET_PRIV(self)->last_meta, "metrics");
|
||||
JsonArray *next_metrics = json_object_get_array_member (GET_PRIV(self)->next_meta, "metrics");
|
||||
if (last_metrics == NULL
|
||||
|| next_metrics == NULL
|
||||
|| json_array_get_length (last_metrics) <= metric
|
||||
|| json_array_get_length (next_metrics) <= metric)
|
||||
return -1;
|
||||
|
||||
JsonObject *last_metric = json_array_get_object_element (last_metrics, metric);
|
||||
JsonObject *next_metric = json_array_get_object_element (next_metrics, metric);
|
||||
if (last_metric == NULL
|
||||
|| next_metric == NULL)
|
||||
return -1;
|
||||
|
||||
JsonArray *last_instances = json_object_get_array_member (last_metric, "instances");
|
||||
JsonArray *next_instances = json_object_get_array_member (next_metric, "instances");
|
||||
if (last_instances == NULL
|
||||
|| next_instances == NULL
|
||||
|| json_array_get_length (next_instances) <= instance)
|
||||
return -1;
|
||||
|
||||
int n_last_instances = json_array_get_length (last_instances);
|
||||
const gchar *next_instance = json_array_get_string_element (next_instances, instance);
|
||||
for (int i = 0; i < n_last_instances; i++)
|
||||
{
|
||||
if (g_strcmp0 (json_array_get_string_element (last_instances, i), next_instance) == 0)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static JsonArray *
|
||||
build_json_data (CockpitMetrics *self, double interpol_r)
|
||||
{
|
||||
JsonArray *output = NULL;
|
||||
JsonNode *node;
|
||||
|
||||
for (int i = 0; i < GET_PRIV(self)->n_metrics; i++)
|
||||
{
|
||||
if (GET_PRIV(self)->metric_info[i].has_instances)
|
||||
{
|
||||
JsonArray *res = NULL;
|
||||
for (int j = 0; j < GET_PRIV(self)->metric_info[i].n_next_instances; j++)
|
||||
res = compute_and_maybe_push_value (self, interpol_r, i, j, find_last_instance (self, i, j), res, j);
|
||||
node = json_node_new (JSON_NODE_ARRAY);
|
||||
json_node_take_array (node, res ? res : json_array_new ());
|
||||
output = push_array_at (output, i, node);
|
||||
}
|
||||
else
|
||||
output = compute_and_maybe_push_value (self, interpol_r, i, 0, (GET_PRIV(self)->meta_reset? -1 : 0), output, i);
|
||||
}
|
||||
|
||||
if (output == NULL)
|
||||
output = json_array_new ();
|
||||
return output;
|
||||
}
|
||||
|
||||
double **
|
||||
cockpit_metrics_get_data_buffer (CockpitMetrics *self)
|
||||
{
|
||||
return GET_PRIV(self)->next_data;
|
||||
}
|
||||
|
||||
/*
|
||||
* cockpit_metrics_send_data:
|
||||
* @self: The CockpitMetrics
|
||||
*
|
||||
* Send metrics data down the channel, possibly doing interframe
|
||||
* compression between what was sent last. The data to send comes
|
||||
* from the buffer returned by @cockpit_metrics_get_data_buffer.
|
||||
*/
|
||||
void
|
||||
cockpit_metrics_send_data (CockpitMetrics *self, gint64 timestamp)
|
||||
{
|
||||
JsonArray *res;
|
||||
double interpol_r = 1.0;
|
||||
|
||||
if (GET_PRIV(self)->message == NULL)
|
||||
GET_PRIV(self)->message = json_array_new ();
|
||||
|
||||
if (GET_PRIV(self)->interpolate && !GET_PRIV(self)->meta_reset)
|
||||
{
|
||||
double interval = ((double)(timestamp - GET_PRIV(self)->last_timestamp));
|
||||
if (interval > 0)
|
||||
{
|
||||
interpol_r = GET_PRIV(self)->meta_interval / interval;
|
||||
timestamp = GET_PRIV(self)->last_timestamp + GET_PRIV(self)->meta_interval;
|
||||
}
|
||||
}
|
||||
|
||||
GET_PRIV(self)->next_timestamp = timestamp;
|
||||
|
||||
res = build_json_data (self, interpol_r);
|
||||
json_array_add_array_element (GET_PRIV(self)->message, res);
|
||||
|
||||
/* Now setup for the next round by swapping buffers and then making
|
||||
sure that the new 'next' buffer has the right layout.
|
||||
*/
|
||||
|
||||
double **t = GET_PRIV(self)->last_data;
|
||||
GET_PRIV(self)->last_data = GET_PRIV(self)->next_data;
|
||||
GET_PRIV(self)->next_data = t;
|
||||
|
||||
if (GET_PRIV(self)->last_meta != GET_PRIV(self)->next_meta)
|
||||
{
|
||||
realloc_next_buffer (self);
|
||||
|
||||
for (int i = 0; i < GET_PRIV(self)->n_metrics; i++)
|
||||
GET_PRIV(self)->metric_info[i].n_last_instances = GET_PRIV(self)->metric_info[i].n_next_instances;
|
||||
|
||||
if (GET_PRIV(self)->last_meta)
|
||||
json_object_unref (GET_PRIV(self)->last_meta);
|
||||
GET_PRIV(self)->last_meta = json_object_ref (GET_PRIV(self)->next_meta);
|
||||
}
|
||||
|
||||
GET_PRIV(self)->derived_valid = TRUE;
|
||||
GET_PRIV(self)->last_timestamp = GET_PRIV(self)->next_timestamp;
|
||||
GET_PRIV(self)->meta_reset = FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_metrics_flush_data (CockpitMetrics *self)
|
||||
{
|
||||
if (GET_PRIV(self)->message)
|
||||
{
|
||||
send_array (self, GET_PRIV(self)->message);
|
||||
json_array_unref (GET_PRIV(self)->message);
|
||||
GET_PRIV(self)->message = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_metrics_set_interpolate (CockpitMetrics *self,
|
||||
gboolean interpolate)
|
||||
{
|
||||
GET_PRIV(self)->interpolate = interpolate;
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_metrics_set_compress (CockpitMetrics *self,
|
||||
gboolean compress)
|
||||
{
|
||||
GET_PRIV(self)->compress = compress;
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
#define COCKPIT_TYPE_METRICS (cockpit_metrics_get_type ())
|
||||
G_DECLARE_DERIVABLE_TYPE(CockpitMetrics, cockpit_metrics, COCKPIT, METRICS, CockpitChannel)
|
||||
|
||||
struct _CockpitMetricsBuffer {
|
||||
int n_elements;
|
||||
double *data;
|
||||
};
|
||||
|
||||
struct _CockpitMetricsClass {
|
||||
CockpitChannelClass parent_class;
|
||||
|
||||
void (* tick) (CockpitMetrics *metrics,
|
||||
gint64 current_monotic_time);
|
||||
|
||||
};
|
||||
|
||||
GType cockpit_metrics_get_type (void) G_GNUC_CONST;
|
||||
|
||||
void cockpit_metrics_set_interpolate (CockpitMetrics *self,
|
||||
gboolean interpolate);
|
||||
|
||||
void cockpit_metrics_set_compress (CockpitMetrics *self,
|
||||
gboolean compress);
|
||||
|
||||
void cockpit_metrics_metronome (CockpitMetrics *self,
|
||||
gint64 interval);
|
||||
|
||||
/* Sending samples
|
||||
*
|
||||
* Derived classes need to call the following functions in a carefully
|
||||
* orchestrated way in order to send out 'meta' and 'data' messages on
|
||||
* the channel.
|
||||
*
|
||||
* The CockpitMetrics class inspects the 'meta' messages and adjusts
|
||||
* its behavior and data structures to it. Derived classes then fill
|
||||
* the 'data buffer' and the CockpitMetrics will post-process it as
|
||||
* requested in the 'meta' message.
|
||||
*/
|
||||
|
||||
/* - cockpit_metrics_send_meta (self, meta, reset)
|
||||
*
|
||||
* Send a 'meta' message. When 'reset' is TRUE, the next data message
|
||||
* is treated as if it were the first on the channel: No compression,
|
||||
* derivation, or interpolation is done for it.
|
||||
*
|
||||
* - buffer = cockpit_metrics_get_data_buffer (self)
|
||||
*
|
||||
* Returns a buffer for depositing sample values. The value for
|
||||
* instance J of metric I should be placed into 'buffer[i][j]'. The
|
||||
* number of metrics and the number of instances of each metric is
|
||||
* determined by the "metrics" member of the meta object passed to the
|
||||
* most recent call to cockpit_metrics_send_meta.
|
||||
*
|
||||
* - cockpit_metrics_send_data (self, timestamp)
|
||||
*
|
||||
* Post-processes the samples in the buffer and queues them for
|
||||
* sending. The 'timestamp' is the number of milliseconds since an
|
||||
* arbitrary epoch. If it is not exactly one interval later than the
|
||||
* value in the previous call to this function, the sample values are
|
||||
* 'warped' in time via linear interpolation. The expected interval
|
||||
* is taken from the most recent 'meta' message.
|
||||
*
|
||||
* - cockpit_metrics_flush_data (self)
|
||||
*
|
||||
* Actually send out all queued samples in a 'data' message.
|
||||
*/
|
||||
|
||||
void cockpit_metrics_send_meta (CockpitMetrics *self,
|
||||
JsonObject *meta,
|
||||
gboolean reset);
|
||||
|
||||
double **cockpit_metrics_get_data_buffer (CockpitMetrics *self);
|
||||
void cockpit_metrics_send_data (CockpitMetrics *self, gint64 timestamp);
|
||||
void cockpit_metrics_flush_data (CockpitMetrics *self);
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitmountsamples.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
#include <fts.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
void
|
||||
cockpit_mount_samples (CockpitSamples *samples)
|
||||
{
|
||||
gchar *contents = NULL;
|
||||
GError *error = NULL;
|
||||
gchar **lines = NULL;
|
||||
gchar *line;
|
||||
gchar *esc_dir, *dir;
|
||||
struct statvfs buf;
|
||||
gint64 total;
|
||||
gsize len;
|
||||
guint n;
|
||||
|
||||
if (!g_file_get_contents ("/proc/mounts", &contents, &len, &error))
|
||||
{
|
||||
g_message ("error loading contents /proc/mounts: %s", error->message);
|
||||
g_error_free (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
lines = g_strsplit (contents, "\n", -1);
|
||||
for (n = 0; lines != NULL && lines[n] != NULL; n++)
|
||||
{
|
||||
line = lines[n];
|
||||
|
||||
if (strlen (line) == 0)
|
||||
continue;
|
||||
|
||||
/* Only look at real devices
|
||||
*/
|
||||
if (line[0] != '/')
|
||||
continue;
|
||||
|
||||
while (*line && !isspace (*line))
|
||||
line++;
|
||||
while (*line && isspace (*line))
|
||||
line++;
|
||||
esc_dir = line;
|
||||
while (*line && !isspace (*line))
|
||||
line++;
|
||||
*line = '\0';
|
||||
|
||||
dir = g_strcompress (esc_dir);
|
||||
|
||||
if (statvfs (dir, &buf) >= 0)
|
||||
{
|
||||
// We explicitly store the fragment size as 64 bits so that
|
||||
// computations with it don't overflow on 32 bit
|
||||
// architectures.
|
||||
|
||||
gint64 frsize = buf.f_frsize;
|
||||
total = frsize * buf.f_blocks;
|
||||
cockpit_samples_sample (samples, "mount.total", dir, total);
|
||||
cockpit_samples_sample (samples, "mount.used", dir, total - frsize * buf.f_bfree);
|
||||
}
|
||||
|
||||
g_free (dir);
|
||||
}
|
||||
|
||||
out:
|
||||
g_strfreev (lines);
|
||||
g_free (contents);
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_MOUNT_SAMPLES_H__
|
||||
#define COCKPIT_MOUNT_SAMPLES_H__
|
||||
|
||||
#include "cockpitsamples.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void cockpit_mount_samples (CockpitSamples *samples);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_MOUNT_SAMPLES_H__ */
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitnetworksamples.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* TODO: this should be optimized so we don't allocate network and call open()/close() all the time */
|
||||
|
||||
void
|
||||
cockpit_network_samples (CockpitSamples *samples)
|
||||
{
|
||||
gchar *contents = NULL;
|
||||
GError *error = NULL;
|
||||
gchar **lines = NULL;
|
||||
gsize len;
|
||||
guint n;
|
||||
|
||||
guint64 total_rx = 0;
|
||||
guint64 total_tx = 0;
|
||||
|
||||
if (!g_file_get_contents ("/proc/net/dev", &contents, &len, &error))
|
||||
{
|
||||
g_warning ("error loading contents /proc/net/dev: %s", error->message);
|
||||
g_error_free (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
lines = g_strsplit (contents, "\n", -1);
|
||||
for (n = 0; lines != NULL && lines[n] != NULL; n++)
|
||||
{
|
||||
const gchar *line = lines[n];
|
||||
gchar iface_name[64]; /* guaranteed to be max 16 chars */
|
||||
guint64 bytes_rx, packets_rx, errors_rx, dropped_rx, fifo_rx, frame_rx, compressed_rx, multicast_rx;
|
||||
guint64 bytes_tx, packets_tx, errors_tx, dropped_tx, fifo_tx, frame_tx, compressed_tx, multicast_tx;
|
||||
gint num_parsed;
|
||||
gchar *ptr;
|
||||
|
||||
/* Format is
|
||||
*
|
||||
* Inter-| Receive | Transmit
|
||||
* face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
|
||||
* lo: 2776770 11307 0 0 0 0 0 0 2776770 11307 0 0 0 0 0 0
|
||||
* eth0: 1215645 2751 0 0 0 0 0 0 1782404 4324 0 0 0 427 0 0
|
||||
* ppp0: 1622270 5552 1 0 0 0 0 0 354130 5669 0 0 0 0 0 0
|
||||
* tap0: 7714 81 0 0 0 0 0 0 7714 81 0 0 0 0 0 0
|
||||
*/
|
||||
|
||||
if (n < 2 || strlen (line) == 0)
|
||||
continue;
|
||||
|
||||
num_parsed = sscanf (line,
|
||||
"%s %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT
|
||||
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT,
|
||||
iface_name,
|
||||
&bytes_rx, &packets_rx, &errors_rx, &dropped_rx,
|
||||
&fifo_rx, &frame_rx, &compressed_rx, &multicast_rx,
|
||||
&bytes_tx, &packets_tx, &errors_tx, &dropped_tx,
|
||||
&fifo_tx, &frame_tx, &compressed_tx, &multicast_tx);
|
||||
if (num_parsed != 17)
|
||||
{
|
||||
g_warning ("Error parsing line %d of file /proc/net/dev (num_parsed=%d): `%s'", n, num_parsed, line);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* remove trailing ':' from interface name */
|
||||
ptr = strrchr (iface_name, ':');
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
|
||||
cockpit_samples_sample (samples, "network.interface.rx", iface_name, bytes_rx);
|
||||
cockpit_samples_sample (samples, "network.interface.tx", iface_name, bytes_tx);
|
||||
|
||||
total_rx += bytes_rx;
|
||||
total_tx += bytes_tx;
|
||||
}
|
||||
|
||||
cockpit_samples_sample (samples, "network.all.rx", NULL, total_rx);
|
||||
cockpit_samples_sample (samples, "network.all.tx", NULL, total_tx);
|
||||
|
||||
out:
|
||||
g_strfreev (lines);
|
||||
g_free (contents);
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_NETWORK_SAMPLES_H__
|
||||
#define COCKPIT_NETWORK_SAMPLES_H__
|
||||
|
||||
#include "cockpitsamples.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void cockpit_network_samples (CockpitSamples *samples);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_NETWORK_SAMPLES_H__ */
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitnullchannel.h"
|
||||
|
||||
/**
|
||||
* CockpitNullChannel:
|
||||
*
|
||||
* A #CockpitChannel that never sends messages and ignores
|
||||
* all messages it receives.
|
||||
*
|
||||
* The payload type for this channel is 'null'.
|
||||
*/
|
||||
|
||||
#define COCKPIT_NULL_CHANNEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_NULL_CHANNEL, CockpitNullChannel))
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
} CockpitNullChannel;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannelClass parent_class;
|
||||
} CockpitNullChannelClass;
|
||||
|
||||
G_DEFINE_TYPE (CockpitNullChannel, cockpit_null_channel, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
cockpit_null_channel_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
g_debug ("received null channel payload");
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_null_channel_init (CockpitNullChannel *self)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_null_channel_prepare (CockpitChannel *channel)
|
||||
{
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_null_channel_parent_class)->prepare (channel);
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_null_channel_class_init (CockpitNullChannelClass *klass)
|
||||
{
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
channel_class->prepare = cockpit_null_channel_prepare;
|
||||
channel_class->recv = cockpit_null_channel_recv;
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_NULL_CHANNEL_H__
|
||||
#define COCKPIT_NULL_CHANNEL_H__
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_NULL_CHANNEL (cockpit_null_channel_get_type ())
|
||||
|
||||
GType cockpit_null_channel_get_type (void) G_GNUC_CONST;
|
||||
|
||||
#endif /* COCKPIT_NULL_CHANEL_H__ */
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_PACKAGES_H_
|
||||
#define COCKPIT_PACKAGES_H_
|
||||
|
||||
#include <glib.h>
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
typedef struct _CockpitPackage CockpitPackage;
|
||||
typedef struct _CockpitPackages CockpitPackages;
|
||||
|
||||
CockpitPackages * cockpit_packages_new (void);
|
||||
|
||||
GIOStream * cockpit_packages_connect (void);
|
||||
|
||||
const gchar * cockpit_packages_get_checksum (CockpitPackages *packages);
|
||||
|
||||
gchar ** cockpit_packages_get_names (CockpitPackages *packages);
|
||||
|
||||
GList * cockpit_packages_get_bridges (CockpitPackages *packages);
|
||||
|
||||
gchar * cockpit_packages_resolve (CockpitPackages *packages,
|
||||
const gchar *name,
|
||||
const gchar *path,
|
||||
CockpitPackage **package);
|
||||
|
||||
void cockpit_packages_reload (CockpitPackages *packages);
|
||||
|
||||
JsonObject * cockpit_packages_peek_json (CockpitPackages *packages);
|
||||
|
||||
void cockpit_packages_dbus_startup (CockpitPackages *packages);
|
||||
|
||||
void cockpit_packages_on_change (CockpitPackages *packages,
|
||||
void (*callback) (gconstpointer user_data),
|
||||
gconstpointer user_data);
|
||||
|
||||
void cockpit_packages_free (CockpitPackages *packages);
|
||||
|
||||
void cockpit_packages_dump (void);
|
||||
|
||||
|
||||
#endif /* COCKPIT_PACKAGES_H_ */
|
||||
|
|
@ -1,679 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitpacketchannel.h"
|
||||
|
||||
#include "cockpitconnect.h"
|
||||
|
||||
#include "common/cockpitflow.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitunicode.h"
|
||||
|
||||
#include <gio/gunixsocketaddress.h>
|
||||
#include <glib-unix.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
/**
|
||||
* CockpitPacketChannel:
|
||||
*
|
||||
* A #CockpitChannel that sends messages from a regular seqpacket socket.
|
||||
* Support for dgram sockets should also fit in here rather well, but is
|
||||
* not implemented at the current time.
|
||||
*
|
||||
* The payload type for this channel is 'stream'.
|
||||
*/
|
||||
|
||||
#define DEF_PACKET_SIZE (64UL * 1024UL)
|
||||
|
||||
/* Sadly this is limited by the max size of our WebSocket payload */
|
||||
#define MAX_PACKET_SIZE (128UL * 1024UL)
|
||||
|
||||
/* Several megabytes is when we start to consider queue full enough */
|
||||
#define QUEUE_PRESSURE (128UL * DEF_PACKET_SIZE)
|
||||
|
||||
enum {
|
||||
CREATED = 0,
|
||||
CONNECTING,
|
||||
RELAYING,
|
||||
CLOSED
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
GMainContext *context;
|
||||
gchar *name;
|
||||
gint state;
|
||||
gsize max_size;
|
||||
|
||||
int fd;
|
||||
GSource *in_source;
|
||||
gboolean in_done;
|
||||
GSource *out_source;
|
||||
GQueue *out_queue;
|
||||
gboolean out_done;
|
||||
gsize out_queued;
|
||||
|
||||
/* Pressure which throttles input on this pipe */
|
||||
CockpitFlow *pressure;
|
||||
gulong pressure_sig;
|
||||
} CockpitPacketChannel;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannelClass parent_class;
|
||||
} CockpitPacketChannelClass;
|
||||
|
||||
static void start_input (CockpitPacketChannel *self);
|
||||
|
||||
static void start_output (CockpitPacketChannel *self);
|
||||
|
||||
static void cockpit_packet_channel_flow_iface (CockpitFlowInterface *iface);
|
||||
|
||||
#define COCKPIT_PACKET_CHANNEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_PACKET_CHANNEL, \
|
||||
CockpitPacketChannel))
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CockpitPacketChannel, cockpit_packet_channel, COCKPIT_TYPE_CHANNEL,
|
||||
G_IMPLEMENT_INTERFACE (COCKPIT_TYPE_FLOW, cockpit_packet_channel_flow_iface));
|
||||
|
||||
static void
|
||||
stop_output (CockpitPacketChannel *self)
|
||||
{
|
||||
g_assert (self->out_source != NULL);
|
||||
g_source_destroy (self->out_source);
|
||||
g_source_unref (self->out_source);
|
||||
self->out_source = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
stop_input (CockpitPacketChannel *self)
|
||||
{
|
||||
g_assert (self->in_source != NULL);
|
||||
g_source_destroy (self->in_source);
|
||||
g_source_unref (self->in_source);
|
||||
self->in_source = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
close_with_errno (CockpitPacketChannel *self,
|
||||
const gchar *message,
|
||||
int errn)
|
||||
{
|
||||
const gchar *problem = NULL;
|
||||
|
||||
if (errn == EPERM || errn == EACCES)
|
||||
problem = "access-denied";
|
||||
else if (errn == ENOENT || errn == ECONNREFUSED)
|
||||
problem = "not-found";
|
||||
|
||||
if (problem)
|
||||
{
|
||||
g_message ("%s: %s: %s", self->name, message, g_strerror (errn));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_warning ("%s: %s: %s", self->name, message, g_strerror (errn));
|
||||
problem = "internal-error";
|
||||
}
|
||||
|
||||
cockpit_channel_close (COCKPIT_CHANNEL (self), problem);
|
||||
}
|
||||
|
||||
static void
|
||||
close_maybe (CockpitPacketChannel *self)
|
||||
{
|
||||
if (self->state < CLOSED)
|
||||
{
|
||||
if (self->in_done && self->out_done)
|
||||
{
|
||||
g_debug ("%s: input and output done", self->name);
|
||||
cockpit_channel_close (COCKPIT_CHANNEL (self), NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dispatch_input (gint fd,
|
||||
GIOCondition cond,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitPacketChannel *self = (CockpitPacketChannel *)user_data;
|
||||
GByteArray *buffer;
|
||||
GBytes *message;
|
||||
gssize ret = 0;
|
||||
int errn;
|
||||
|
||||
g_return_val_if_fail (self->in_source, FALSE);
|
||||
|
||||
buffer = g_byte_array_new ();
|
||||
|
||||
/*
|
||||
* Enable clean shutdown by not reading when we just get
|
||||
* G_IO_HUP. Note that when we get G_IO_ERR we do want to read
|
||||
* just so we can get the appropriate detailed error message.
|
||||
*/
|
||||
if (cond != G_IO_HUP)
|
||||
{
|
||||
g_byte_array_set_size (buffer, self->max_size);
|
||||
g_debug ("%s: reading input %x", self->name, cond);
|
||||
ret = recv (self->fd, buffer->data, buffer->len, 0);
|
||||
|
||||
errn = errno;
|
||||
if (ret < 0)
|
||||
{
|
||||
if (errn == EAGAIN || errn == EINTR)
|
||||
{
|
||||
g_byte_array_free (buffer, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
else if (errn == ECONNRESET)
|
||||
{
|
||||
g_debug ("couldn't read: %s", g_strerror (errn));
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_byte_array_free (buffer, TRUE);
|
||||
close_with_errno (self, "couldn't read", errn);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
g_byte_array_set_size (buffer, ret);
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
g_debug ("%s: end of input", self->name);
|
||||
cockpit_channel_control (COCKPIT_CHANNEL (self), "done", NULL);
|
||||
self->in_done = TRUE;
|
||||
stop_input (self);
|
||||
}
|
||||
|
||||
g_object_ref (self);
|
||||
|
||||
message = g_byte_array_free_to_bytes (buffer);
|
||||
cockpit_channel_send (COCKPIT_CHANNEL (self), message, FALSE);
|
||||
g_bytes_unref (message);
|
||||
|
||||
if (self->in_done)
|
||||
close_maybe (self);
|
||||
|
||||
g_object_unref (self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dispatch_connect (CockpitPacketChannel *self)
|
||||
{
|
||||
socklen_t slen;
|
||||
int error;
|
||||
|
||||
slen = sizeof (error);
|
||||
if (getsockopt (self->fd, SOL_SOCKET, SO_ERROR, &error, &slen) != 0)
|
||||
{
|
||||
g_warning ("%s: couldn't get connection result", self->name);
|
||||
cockpit_channel_close (COCKPIT_CHANNEL (self), "internal-error");
|
||||
}
|
||||
else if (error == EINPROGRESS)
|
||||
{
|
||||
/* keep connecting */
|
||||
}
|
||||
else if (error != 0)
|
||||
{
|
||||
close_with_errno (self, "couldn't connect", error);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->state = RELAYING;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dispatch_output (gint fd,
|
||||
GIOCondition cond,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitPacketChannel *self = (CockpitPacketChannel *)user_data;
|
||||
gconstpointer data;
|
||||
gsize before, size;
|
||||
gssize ret;
|
||||
|
||||
/* A non-blocking connect is processed here */
|
||||
if (self->state == CONNECTING && !dispatch_connect (self))
|
||||
return TRUE;
|
||||
|
||||
g_return_val_if_fail (self->out_source, FALSE);
|
||||
|
||||
before = self->out_queued;
|
||||
|
||||
while (self->out_queue->head)
|
||||
{
|
||||
data = g_bytes_get_data (self->out_queue->head->data, &size);
|
||||
ret = send (self->fd, data, size, 0);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EINTR || errno == ENOBUFS)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
close_with_errno (self, "couldn't write", errno);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_bytes_unref (g_queue_pop_head (self->out_queue));
|
||||
g_assert (size <= self->out_queued);
|
||||
self->out_queued -= size;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're controlling another flow, turn it on again when our output
|
||||
* buffer size becomes less than the low mark.
|
||||
*/
|
||||
if (before >= QUEUE_PRESSURE && self->out_queued < QUEUE_PRESSURE)
|
||||
cockpit_flow_emit_pressure (COCKPIT_FLOW (self), FALSE);
|
||||
|
||||
if (self->out_queue->head)
|
||||
return TRUE;
|
||||
|
||||
g_debug ("%s: output queue empty", self->name);
|
||||
|
||||
/* If all messages are done, then stop polling out fd */
|
||||
stop_output (self);
|
||||
|
||||
if (self->out_done)
|
||||
{
|
||||
g_debug ("%s: end of output", self->name);
|
||||
|
||||
/* And if closing, then we need to shutdown the output fd */
|
||||
if (shutdown (self->fd, SHUT_WR) < 0)
|
||||
close_with_errno (self, "couldn't shutdown fd", errno);
|
||||
}
|
||||
|
||||
close_maybe (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
CockpitPacketChannel *self = COCKPIT_PACKET_CHANNEL (channel);
|
||||
gsize before, size;
|
||||
|
||||
if (self->state >= CLOSED)
|
||||
return;
|
||||
|
||||
size = g_bytes_get_size (message);
|
||||
before = self->out_queued;
|
||||
g_return_if_fail (G_MAXSIZE - size > self->out_queued);
|
||||
self->out_queued += size;
|
||||
g_queue_push_tail (self->out_queue, g_bytes_ref (message));
|
||||
|
||||
/*
|
||||
* If we have too much data queued, and are controlling another flow
|
||||
* tell it to stop sending data, each time we cross over the high bound.
|
||||
*/
|
||||
if (before < QUEUE_PRESSURE && self->out_queued >= QUEUE_PRESSURE)
|
||||
cockpit_flow_emit_pressure (COCKPIT_FLOW (self), TRUE);
|
||||
|
||||
if (!self->out_source && self->fd >= 0)
|
||||
start_output (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cockpit_packet_channel_control (CockpitChannel *channel,
|
||||
const gchar *command,
|
||||
JsonObject *message)
|
||||
{
|
||||
CockpitPacketChannel *self = COCKPIT_PACKET_CHANNEL (channel);
|
||||
gboolean ret = TRUE;
|
||||
gint64 size = 0;
|
||||
|
||||
/* New set of options for channel */
|
||||
if (g_str_equal (command, "options"))
|
||||
{
|
||||
if (!cockpit_json_get_int (message, "max-size", self->max_size, &size) ||
|
||||
size < 1 || size > MAX_PACKET_SIZE)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"max-size\" option for channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
self->max_size = size;
|
||||
}
|
||||
|
||||
/* Channel input from frontend is done */
|
||||
else if (g_str_equal (command, "done"))
|
||||
{
|
||||
self->out_done = TRUE;
|
||||
if (!self->out_source)
|
||||
start_output (self);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
ret = FALSE;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_close (CockpitChannel *channel,
|
||||
const gchar *problem)
|
||||
{
|
||||
CockpitPacketChannel *self = COCKPIT_PACKET_CHANNEL (channel);
|
||||
|
||||
if (self->state >= CLOSED)
|
||||
return;
|
||||
|
||||
self->state = CLOSED;
|
||||
|
||||
if (self->in_source)
|
||||
stop_input (self);
|
||||
self->in_done = TRUE;
|
||||
if (self->out_source)
|
||||
stop_output (self);
|
||||
self->out_done = TRUE;
|
||||
|
||||
if (self->fd != -1)
|
||||
{
|
||||
close (self->fd);
|
||||
self->fd = -1;
|
||||
}
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_packet_channel_parent_class)->close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_init (CockpitPacketChannel *self)
|
||||
{
|
||||
self->fd = -1;
|
||||
self->state = CREATED;
|
||||
self->max_size = DEF_PACKET_SIZE;
|
||||
self->out_queue = g_queue_new ();
|
||||
self->context = g_main_context_ref_thread_default ();
|
||||
}
|
||||
|
||||
static int
|
||||
packet_channel_connect (CockpitPacketChannel *self,
|
||||
GSocketAddress *address)
|
||||
{
|
||||
gsize native_len;
|
||||
gpointer native;
|
||||
int sock = -1;
|
||||
|
||||
g_return_val_if_fail (G_IS_SOCKET_ADDRESS (address), -1);
|
||||
|
||||
sock = socket (g_socket_address_get_family (address), SOCK_SEQPACKET, 0);
|
||||
if (sock < 0)
|
||||
{
|
||||
close_with_errno (self, "couldn't open socket", errno);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!g_unix_set_fd_nonblocking (sock, TRUE, NULL))
|
||||
{
|
||||
close (sock);
|
||||
g_return_val_if_reached (-1);
|
||||
}
|
||||
|
||||
native_len = g_socket_address_get_native_size (address);
|
||||
native = g_malloc (native_len);
|
||||
if (!g_socket_address_to_native (address, native, native_len, NULL))
|
||||
{
|
||||
close (sock);
|
||||
g_return_val_if_reached (-1);
|
||||
}
|
||||
if (connect (sock, native, native_len) < 0)
|
||||
{
|
||||
if (errno == EINPROGRESS)
|
||||
{
|
||||
self->state = CONNECTING;
|
||||
}
|
||||
else
|
||||
{
|
||||
close_with_errno (self, "couldn't connect", errno);
|
||||
close (sock);
|
||||
sock = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self->state = RELAYING;
|
||||
}
|
||||
g_free (native);
|
||||
}
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
static void
|
||||
start_output (CockpitPacketChannel *self)
|
||||
{
|
||||
g_assert (self->out_source == NULL);
|
||||
self->out_source = g_unix_fd_source_new (self->fd, G_IO_OUT);
|
||||
g_source_set_name (self->out_source, "packet-output");
|
||||
g_source_set_callback (self->out_source, (GSourceFunc)dispatch_output, self, NULL);
|
||||
g_source_attach (self->out_source, self->context);
|
||||
}
|
||||
|
||||
static void
|
||||
start_input (CockpitPacketChannel *self)
|
||||
{
|
||||
g_assert (self->in_source == NULL);
|
||||
self->in_source = g_unix_fd_source_new (self->fd, G_IO_IN);
|
||||
g_source_set_name (self->in_source, "packet-input");
|
||||
g_source_set_callback (self->in_source, (GSourceFunc)dispatch_input, self, NULL);
|
||||
g_source_attach (self->in_source, self->context);
|
||||
}
|
||||
|
||||
static void
|
||||
on_throttle_pressure (GObject *object,
|
||||
gboolean throttle,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitPacketChannel *self = COCKPIT_PACKET_CHANNEL (user_data);
|
||||
if (throttle)
|
||||
{
|
||||
if (self->in_source != NULL)
|
||||
{
|
||||
g_debug ("%s: applying back pressure in pipe", self->name);
|
||||
stop_input (self);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self->in_source == NULL && !self->in_done)
|
||||
{
|
||||
g_debug ("%s: relieving back pressure in pipe", self->name);
|
||||
start_input (self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_throttle (CockpitFlow *flow,
|
||||
CockpitFlow *controlling)
|
||||
{
|
||||
CockpitPacketChannel *self = COCKPIT_PACKET_CHANNEL (flow);
|
||||
|
||||
if (self->pressure)
|
||||
{
|
||||
g_signal_handler_disconnect (self->pressure, self->pressure_sig);
|
||||
g_object_remove_weak_pointer (G_OBJECT (self->pressure), (gpointer *)&self->pressure);
|
||||
self->pressure = NULL;
|
||||
}
|
||||
|
||||
if (controlling)
|
||||
{
|
||||
self->pressure = controlling;
|
||||
g_object_add_weak_pointer (G_OBJECT (self->pressure), (gpointer *)&self->pressure);
|
||||
self->pressure_sig = g_signal_connect (controlling, "pressure", G_CALLBACK (on_throttle_pressure), self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_flow_iface (CockpitFlowInterface *iface)
|
||||
{
|
||||
iface->throttle = cockpit_packet_channel_throttle;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_prepare (CockpitChannel *channel)
|
||||
{
|
||||
CockpitPacketChannel *self = COCKPIT_PACKET_CHANNEL (channel);
|
||||
GSocketAddress *address;
|
||||
JsonObject *options;
|
||||
int sock;
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_packet_channel_parent_class)->prepare (channel);
|
||||
options = cockpit_channel_get_options (channel);
|
||||
|
||||
/* Support our options in the open message too */
|
||||
cockpit_packet_channel_control (channel, "options", options);
|
||||
if (self->state >= CLOSED)
|
||||
return;
|
||||
|
||||
address = cockpit_connect_parse_address (channel, &self->name);
|
||||
if (!address)
|
||||
{
|
||||
cockpit_channel_close (channel, "internal-error");
|
||||
return;
|
||||
}
|
||||
|
||||
sock = packet_channel_connect (self, address);
|
||||
g_object_unref (address);
|
||||
|
||||
if (sock < 0)
|
||||
{
|
||||
cockpit_channel_close (channel, "internal-error");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->fd = sock;
|
||||
start_input (self);
|
||||
start_output (self);
|
||||
}
|
||||
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_dispose (GObject *object)
|
||||
{
|
||||
CockpitPacketChannel *self = COCKPIT_PACKET_CHANNEL (object);
|
||||
|
||||
cockpit_packet_channel_throttle (COCKPIT_FLOW (self), NULL);
|
||||
g_assert (self->pressure == NULL);
|
||||
|
||||
if (self->state < CLOSED)
|
||||
cockpit_channel_close (COCKPIT_CHANNEL (self), "terminated");
|
||||
|
||||
while (self->out_queue->head)
|
||||
g_bytes_unref (g_queue_pop_head (self->out_queue));
|
||||
self->out_queued = 0;
|
||||
|
||||
G_OBJECT_CLASS (cockpit_packet_channel_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_finalize (GObject *object)
|
||||
{
|
||||
CockpitPacketChannel *self = COCKPIT_PACKET_CHANNEL (object);
|
||||
g_assert (self->state == CLOSED);
|
||||
g_assert (self->fd < 0);
|
||||
g_assert (!self->in_source);
|
||||
g_assert (!self->out_source);
|
||||
g_queue_free (self->out_queue);
|
||||
g_free (self->name);
|
||||
|
||||
if (self->context)
|
||||
g_main_context_unref (self->context);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_packet_channel_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_packet_channel_class_init (CockpitPacketChannelClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = cockpit_packet_channel_dispose;
|
||||
gobject_class->finalize = cockpit_packet_channel_finalize;
|
||||
|
||||
channel_class->prepare = cockpit_packet_channel_prepare;
|
||||
channel_class->control = cockpit_packet_channel_control;
|
||||
channel_class->recv = cockpit_packet_channel_recv;
|
||||
channel_class->close = cockpit_packet_channel_close;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_packet_channel_open:
|
||||
* @transport: the transport to send/receive messages on
|
||||
* @channel_id: the channel id
|
||||
* @unix_path: the UNIX socket path to communicate with
|
||||
*
|
||||
* This function is mainly used by tests. The usual way
|
||||
* to get a #CockpitPacketChannel is via cockpit_channel_open()
|
||||
*
|
||||
* Returns: (transfer full): the new channel
|
||||
*/
|
||||
CockpitChannel *
|
||||
cockpit_packet_channel_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *unix_path)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
|
||||
g_return_val_if_fail (channel_id != NULL, NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_string_member (options, "unix", unix_path);
|
||||
json_object_set_string_member (options, "payload", "packet");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_PACKET_CHANNEL,
|
||||
"transport", transport,
|
||||
"id", channel_id,
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
return channel;
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_PACKET_CHANNEL_H__
|
||||
#define COCKPIT_PACKET_CHANNEL_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_PACKET_CHANNEL (cockpit_packet_channel_get_type ())
|
||||
|
||||
GType cockpit_packet_channel_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitChannel * cockpit_packet_channel_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *unix_path);
|
||||
|
||||
#endif /* COCKPIT_PACKET_CHANNEL_H__ */
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitpaths.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
gboolean
|
||||
cockpit_path_has_parent (const gchar *path,
|
||||
const gchar *parent)
|
||||
{
|
||||
gsize length = strlen (parent);
|
||||
const gchar *last;
|
||||
|
||||
if (length == 1 && parent[0] == '/' && path[0])
|
||||
last = path + 1;
|
||||
|
||||
else if (strncmp (path, parent, length) == 0 && path[length] == '/')
|
||||
last = path + length + 1;
|
||||
|
||||
else
|
||||
return FALSE;
|
||||
|
||||
return strchr (last, '/') == NULL;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_path_equal_or_ancestor (const gchar *path,
|
||||
const gchar *ancestor)
|
||||
{
|
||||
gsize length = strlen (ancestor);
|
||||
if (length == 1 && ancestor[0] == '/')
|
||||
return TRUE;
|
||||
|
||||
if (strncmp (path, ancestor, length) == 0 &&
|
||||
(path[length] == '/' || path[length] == '\0'))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_path_has_ancestor (const gchar *path,
|
||||
const gchar *ancestor)
|
||||
{
|
||||
gsize length = strlen (ancestor);
|
||||
if (length == 1 && ancestor[0] == '/')
|
||||
return TRUE;
|
||||
|
||||
if (strncmp (path, ancestor, length) == 0 &&
|
||||
path[length] == '/')
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
gsize len;
|
||||
const gchar *data;
|
||||
} PathData;
|
||||
|
||||
static gint
|
||||
tree_path_cmp (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
const PathData *pa = a;
|
||||
const PathData *pb = b;
|
||||
gsize la = pa->len;
|
||||
gsize lb = pb->len;
|
||||
gint ret;
|
||||
|
||||
ret = memcmp (pa->data, pb->data, MIN (la, lb));
|
||||
if (ret == 0 && la != lb)
|
||||
ret = (la < lb) ? -1 : 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gint
|
||||
tree_prefix_search (gconstpointer a,
|
||||
gconstpointer b)
|
||||
{
|
||||
const PathData *pa = a;
|
||||
const PathData *pb = b;
|
||||
gsize la = pa->len;
|
||||
gsize lb = pb->len;
|
||||
gint ret;
|
||||
|
||||
/* Yes, g_tree_search() means this is backwards */
|
||||
ret = memcmp (pb->data, pa->data, MIN (la, lb));
|
||||
if (ret == 0 && la != lb)
|
||||
{
|
||||
if (la > lb)
|
||||
{
|
||||
if ((lb == 1 && pb->data[0] == '/') || pa->data[lb] == '/')
|
||||
ret = 0;
|
||||
else
|
||||
ret = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
GTree *
|
||||
cockpit_paths_new (void)
|
||||
{
|
||||
return g_tree_new_full (tree_path_cmp, NULL, g_free, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* cockpit_paths_add:
|
||||
* @tree: The tree to add to
|
||||
* @path: The path to add
|
||||
*
|
||||
* Adds the path if this path or a parent is not already
|
||||
* in the tree. Will return %NULL if the path is already
|
||||
* in the tree ... otherwise will return the internally
|
||||
* reallocated path.
|
||||
*/
|
||||
const gchar *
|
||||
cockpit_paths_add (GTree *tree,
|
||||
const gchar *path)
|
||||
{
|
||||
PathData key = { strlen (path), path };
|
||||
PathData *pd = g_tree_lookup (tree, &key);
|
||||
|
||||
if (!pd)
|
||||
{
|
||||
pd = g_malloc (sizeof (PathData) + key.len + 1);
|
||||
pd->len = key.len;
|
||||
pd->data = (gchar *)(pd + 1);
|
||||
memcpy ((gchar *)pd->data, path, key.len + 1);
|
||||
g_tree_replace (tree, pd, pd);
|
||||
return pd->data;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_paths_remove (GTree *tree,
|
||||
const gchar *path)
|
||||
{
|
||||
PathData key = { strlen (path), path };
|
||||
return g_tree_remove (tree, &key);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
cockpit_paths_contain (GTree *tree,
|
||||
const gchar *path)
|
||||
{
|
||||
PathData key = { strlen(path), path };
|
||||
PathData *pd = g_tree_lookup (tree, &key);
|
||||
return pd ? pd->data : NULL;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_paths_contain_or_descendant (GTree *tree,
|
||||
const gchar *path)
|
||||
{
|
||||
PathData key = { strlen (path), path };
|
||||
PathData *pd = g_tree_search (tree, tree_prefix_search, &key);
|
||||
return pd != NULL;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
cockpit_paths_contain_or_ancestor (GTree *tree,
|
||||
const gchar *path)
|
||||
{
|
||||
PathData key = { strlen (path), path };
|
||||
gboolean last = FALSE;
|
||||
const gchar *pos;
|
||||
PathData *pd;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
pd = g_tree_lookup (tree, &key);
|
||||
if (pd)
|
||||
return pd->data;
|
||||
if (last)
|
||||
return NULL;
|
||||
pos = memrchr (path, '/', key.len);
|
||||
if (!pos)
|
||||
return NULL;
|
||||
if (path == pos)
|
||||
{
|
||||
key.len = 1;
|
||||
last = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
key.len = (pos - path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_PATHS_H
|
||||
#define COCKPIT_PATHS_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/*
|
||||
* These paths operate on normalized paths. Nothing relative, no
|
||||
* .. or anything like that. Each path must start with '/' and the
|
||||
* only path that can end with '/' is the root path.
|
||||
*/
|
||||
|
||||
gboolean cockpit_path_has_ancestor (const gchar *path,
|
||||
const gchar *ancestor);
|
||||
|
||||
gboolean cockpit_path_has_parent (const gchar *path,
|
||||
const gchar *parent);
|
||||
|
||||
gboolean cockpit_path_equal_or_ancestor (const gchar *path,
|
||||
const gchar *ancestor);
|
||||
|
||||
GTree * cockpit_paths_new (void);
|
||||
|
||||
const gchar * cockpit_paths_add (GTree *paths,
|
||||
const gchar *path);
|
||||
|
||||
gboolean cockpit_paths_remove (GTree *paths,
|
||||
const gchar *path);
|
||||
|
||||
const gchar * cockpit_paths_contain (GTree *paths,
|
||||
const gchar *path);
|
||||
|
||||
/* path is in paths or a descendant of path is in paths */
|
||||
gboolean cockpit_paths_contain_or_descendant (GTree *paths,
|
||||
const gchar *path);
|
||||
|
||||
/* path is in paths or an ancestor of path is in paths */
|
||||
const gchar * cockpit_paths_contain_or_ancestor (GTree *paths,
|
||||
const gchar *path);
|
||||
|
||||
#endif /* COCKPIT_PATHS_H */
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitinternalmetrics.h"
|
||||
#include "cockpitpcpmetrics.h"
|
||||
#include "cockpitrouter.h"
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
#include "common/cockpithacks-glib.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitpipetransport.h"
|
||||
#include "common/cockpitsystem.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include <glib-unix.h>
|
||||
|
||||
/* This program is run on each managed server, with the credentials
|
||||
of the user that is logged into the Server Console.
|
||||
*/
|
||||
|
||||
static void
|
||||
send_init_command (CockpitTransport *transport)
|
||||
{
|
||||
const gchar *response = "{ \"command\": \"init\", \"version\": 1 }";
|
||||
GBytes *bytes = g_bytes_new_static (response, strlen (response));
|
||||
cockpit_transport_send (transport, NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
static void
|
||||
add_router_channels (CockpitRouter *router)
|
||||
{
|
||||
JsonObject *match;
|
||||
|
||||
match = json_object_new ();
|
||||
json_object_set_string_member (match, "payload", "metrics1");
|
||||
cockpit_router_add_channel (router, match, cockpit_pcp_metrics_get_type);
|
||||
json_object_unref (match);
|
||||
|
||||
match = json_object_new ();
|
||||
json_object_set_string_member (match, "payload", "metrics1");
|
||||
json_object_set_string_member (match, "source", "internal");
|
||||
cockpit_router_add_channel (router, match, cockpit_internal_metrics_get_type);
|
||||
json_object_unref (match);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_signal_done (gpointer data)
|
||||
{
|
||||
gboolean *closed = data;
|
||||
*closed = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_closed_set_flag (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gboolean *flag = user_data;
|
||||
*flag = TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char **argv)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
CockpitRouter *router;
|
||||
gboolean terminated = FALSE;
|
||||
gboolean closed = FALSE;
|
||||
GOptionContext *context;
|
||||
GError *error = NULL;
|
||||
guint sig_term;
|
||||
|
||||
static GOptionEntry entries[] = {
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
signal (SIGPIPE, SIG_IGN);
|
||||
|
||||
cockpit_setenv_check ("GSETTINGS_BACKEND", "memory", TRUE);
|
||||
cockpit_setenv_check ("GIO_USE_PROXY_RESOLVER", "dummy", TRUE);
|
||||
cockpit_setenv_check ("GIO_USE_VFS", "local", TRUE);
|
||||
|
||||
context = g_option_context_new (NULL);
|
||||
g_option_context_add_main_entries (context, entries, NULL);
|
||||
g_option_context_set_description (context, "cockpit-pcp is run automatically inside of a Cockpit session.\n");
|
||||
|
||||
g_option_context_parse (context, &argc, &argv, &error);
|
||||
g_option_context_free (context);
|
||||
|
||||
if (error)
|
||||
{
|
||||
g_printerr ("cockpit-pcp: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (isatty (1))
|
||||
{
|
||||
g_printerr ("cockpit-pcp: only run from cockpit-bridge\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
cockpit_hacks_redirect_gdebug_to_stderr ();
|
||||
|
||||
sig_term = g_unix_signal_add (SIGTERM, on_signal_done, &terminated);
|
||||
|
||||
transport = cockpit_pipe_transport_new_fds ("stdio", 0, 1);
|
||||
|
||||
router = cockpit_router_new (transport, NULL, NULL);
|
||||
add_router_channels (router);
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
send_init_command (transport);
|
||||
|
||||
while (!closed && !terminated)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_object_unref (router);
|
||||
g_object_unref (transport);
|
||||
|
||||
g_source_remove (sig_term);
|
||||
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_PCP_METRICS_H__
|
||||
#define COCKPIT_PCP_METRICS_H__
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_PCP_METRICS (cockpit_pcp_metrics_get_type ())
|
||||
|
||||
GType cockpit_pcp_metrics_get_type (void) G_GNUC_CONST;
|
||||
|
||||
#endif /* COCKPIT_PCP_METRICS_H__ */
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2017 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __COCKPIT_PEER_H__
|
||||
#define __COCKPIT_PEER_H__
|
||||
|
||||
#include "common/cockpittransport.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_PEER (cockpit_peer_get_type ())
|
||||
#define COCKPIT_PEER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_PEER, CockpitPeer))
|
||||
#define COCKPIT_IS_PEER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), COCKPIT_TYPE_PEER))
|
||||
|
||||
typedef struct _CockpitPeer CockpitPeer;
|
||||
|
||||
typedef void CockpitPeerDoneFunction (const gchar *error, const gchar *stderr, gpointer data);
|
||||
|
||||
typedef struct _CockpitPeerClass {
|
||||
GObjectClass parent_class;
|
||||
|
||||
/* signal */
|
||||
|
||||
void (* closed) (CockpitPeer *peer,
|
||||
const gchar *problem);
|
||||
|
||||
} CockpitPeerClass;
|
||||
|
||||
GType cockpit_peer_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitPeer * cockpit_peer_new (CockpitTransport *transport,
|
||||
JsonObject *config);
|
||||
|
||||
CockpitTransport * cockpit_peer_ensure (CockpitPeer *peer);
|
||||
|
||||
CockpitTransport * cockpit_peer_ensure_with_done (CockpitPeer *peer,
|
||||
CockpitPeerDoneFunction *done_function,
|
||||
gpointer done_data);
|
||||
|
||||
gboolean cockpit_peer_handle (CockpitPeer *peer,
|
||||
const gchar *channel,
|
||||
JsonObject *options,
|
||||
GBytes *data);
|
||||
|
||||
void cockpit_peer_reset (CockpitPeer *peer);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __COCKPIT_PEER_H__ */
|
||||
|
|
@ -1,635 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitpipechannel.h"
|
||||
|
||||
#include "cockpitconnect.h"
|
||||
|
||||
#include "common/cockpitflow.h"
|
||||
#include "common/cockpitpipe.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitunicode.h"
|
||||
#include "common/cockpitunixsignal.h"
|
||||
|
||||
#include <gio/gunixsocketaddress.h>
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
/**
|
||||
* CockpitPipeChannel:
|
||||
*
|
||||
* A #CockpitChannel that sends messages from a regular socket
|
||||
* or file descriptor. Any data is read in whatever chunks it
|
||||
* shows up in read().
|
||||
*
|
||||
* Only UTF8 text data is transmitted. Anything else is
|
||||
* forced into UTF8 by replacing invalid characters.
|
||||
*
|
||||
* The payload type for this channel is 'stream'.
|
||||
*/
|
||||
|
||||
#define COCKPIT_PIPE_CHANNEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_PIPE_CHANNEL, CockpitPipeChannel))
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
CockpitPipe *pipe;
|
||||
GSocket *sock;
|
||||
gchar *name;
|
||||
gboolean open;
|
||||
gboolean closing;
|
||||
guint sig_read;
|
||||
guint sig_close;
|
||||
gint64 batch;
|
||||
gint64 latency;
|
||||
guint timeout;
|
||||
gboolean pty;
|
||||
} CockpitPipeChannel;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannelClass parent_class;
|
||||
} CockpitPipeChannelClass;
|
||||
|
||||
G_DEFINE_TYPE (CockpitPipeChannel, cockpit_pipe_channel, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
GHashTable *internal_fds;
|
||||
|
||||
static gboolean
|
||||
steal_internal_fd (const gchar *name,
|
||||
gint *fdp)
|
||||
{
|
||||
gpointer key;
|
||||
gpointer value;
|
||||
|
||||
if (!internal_fds)
|
||||
return FALSE;
|
||||
|
||||
if (!g_hash_table_lookup_extended (internal_fds, name, &key, &value))
|
||||
return FALSE;
|
||||
|
||||
g_hash_table_steal (internal_fds, key);
|
||||
g_free (key);
|
||||
|
||||
*fdp = GPOINTER_TO_INT (value);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_pipe_channel_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
CockpitPipeChannel *self = COCKPIT_PIPE_CHANNEL (channel);
|
||||
if (self->open)
|
||||
cockpit_pipe_write (self->pipe, message);
|
||||
}
|
||||
|
||||
static void
|
||||
process_pipe_buffer (CockpitPipeChannel *self,
|
||||
GByteArray *data)
|
||||
{
|
||||
CockpitChannel *channel = (CockpitChannel *)self;
|
||||
GBytes *message;
|
||||
|
||||
if (!data && self->pipe)
|
||||
data = cockpit_pipe_get_buffer (self->pipe);
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
if (self->timeout)
|
||||
{
|
||||
g_source_remove (self->timeout);
|
||||
self->timeout = 0;
|
||||
}
|
||||
|
||||
if (data->len)
|
||||
{
|
||||
/* When array is reffed, this just clears byte array */
|
||||
g_byte_array_ref (data);
|
||||
message = g_byte_array_free_to_bytes (data);
|
||||
cockpit_channel_send (channel, message, FALSE);
|
||||
g_bytes_unref (message);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cockpit_pipe_channel_read_window_size_options (JsonObject *options,
|
||||
gushort default_rows,
|
||||
gushort default_cols,
|
||||
gushort *rowsp,
|
||||
gushort *colsp)
|
||||
{
|
||||
gint64 rows, cols;
|
||||
JsonObject *window;
|
||||
|
||||
if (!cockpit_json_get_object (options, "window", NULL, &window))
|
||||
return FALSE;
|
||||
|
||||
if (window == NULL)
|
||||
{
|
||||
*rowsp = default_rows;
|
||||
*colsp = default_cols;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (cockpit_json_get_int (window, "rows", default_rows, &rows) &&
|
||||
cockpit_json_get_int (window, "cols", default_cols, &cols))
|
||||
{
|
||||
*rowsp = (gushort) CLAMP (rows, 0, G_MAXUINT16);
|
||||
*colsp = (gushort) CLAMP (cols, 0, G_MAXUINT16);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cockpit_pipe_channel_control (CockpitChannel *channel,
|
||||
const gchar *command,
|
||||
JsonObject *message)
|
||||
{
|
||||
CockpitPipeChannel *self = COCKPIT_PIPE_CHANNEL (channel);
|
||||
gboolean ret = TRUE;
|
||||
|
||||
/* New set of options for channel */
|
||||
if (g_str_equal (command, "options"))
|
||||
{
|
||||
if (!cockpit_json_get_int (message, "batch", self->batch, &self->batch))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"batch\" option for stream channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!cockpit_json_get_int (message, "latency", self->latency, &self->latency) ||
|
||||
self->latency < 0 || self->latency >= G_MAXUINT)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"latency\" option for stream channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* ignore size options if this channel is not a pty or we are in prepare() */
|
||||
if (self->pty && self->pipe)
|
||||
{
|
||||
gushort rows, cols;
|
||||
|
||||
if (!cockpit_pipe_channel_read_window_size_options (message, 0, 0, &rows, &cols))
|
||||
{
|
||||
g_warning ("%s: invalid \"window.rows\" or \"window.cols\" option for stream channel", self->name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rows > 0 && cols > 0)
|
||||
{
|
||||
gint fd;
|
||||
|
||||
g_object_get (self->pipe, "in-fd", &fd, NULL);
|
||||
if (fd >= 0)
|
||||
{
|
||||
struct winsize size = { rows, cols, 0, 0 };
|
||||
|
||||
if (ioctl (fd, TIOCSWINSZ, &size) < 0)
|
||||
g_warning ("cannot set terminal size for stream channel: %s", g_strerror (errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process_pipe_buffer (self, NULL);
|
||||
}
|
||||
|
||||
/* Channel input is done */
|
||||
else if (g_str_equal (command, "done"))
|
||||
{
|
||||
self->closing = TRUE;
|
||||
process_pipe_buffer (self, NULL);
|
||||
|
||||
/*
|
||||
* If closed, call base class handler directly. Otherwise ask
|
||||
* our pipe to close first, which will come back here.
|
||||
*/
|
||||
if (self->open)
|
||||
cockpit_pipe_close (self->pipe, NULL);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
ret = FALSE;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_pipe_channel_close (CockpitChannel *channel,
|
||||
const gchar *problem)
|
||||
{
|
||||
CockpitPipeChannel *self = COCKPIT_PIPE_CHANNEL (channel);
|
||||
|
||||
self->closing = TRUE;
|
||||
process_pipe_buffer (self, NULL);
|
||||
|
||||
/*
|
||||
* If closed, call base class handler directly. Otherwise ask
|
||||
* our pipe to close first, which will come back here.
|
||||
*/
|
||||
if (self->open)
|
||||
cockpit_pipe_close (self->pipe, problem);
|
||||
else
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_pipe_channel_parent_class)->close (channel, problem);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_batch_timeout (gpointer user_data)
|
||||
{
|
||||
CockpitPipeChannel *self = user_data;
|
||||
self->timeout = 0;
|
||||
process_pipe_buffer (self, NULL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_pipe_read (CockpitPipe *pipe,
|
||||
GByteArray *data,
|
||||
gboolean end_of_data,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitPipeChannel *self = user_data;
|
||||
|
||||
if (!end_of_data && self->batch > 0 && data->len < self->batch)
|
||||
{
|
||||
/* Delay the processing of this data */
|
||||
if (!self->timeout)
|
||||
self->timeout = g_timeout_add (self->latency, on_batch_timeout, self);
|
||||
}
|
||||
else
|
||||
{
|
||||
process_pipe_buffer (self, data);
|
||||
}
|
||||
|
||||
/* Close the pipe when writing is done */
|
||||
if (end_of_data && self->open)
|
||||
{
|
||||
g_debug ("%s: end of data, closing pipe", self->name);
|
||||
cockpit_pipe_close (pipe, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
return_stderr_message (CockpitChannel *channel,
|
||||
CockpitPipe *pipe)
|
||||
{
|
||||
JsonObject *options;
|
||||
gchar *data;
|
||||
|
||||
data = cockpit_pipe_take_stderr_as_utf8 (pipe);
|
||||
if (data == NULL)
|
||||
return;
|
||||
|
||||
options = cockpit_channel_close_options (channel);
|
||||
json_object_set_string_member (options, "message", data);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
on_pipe_close (CockpitPipe *pipe,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitPipeChannel *self = user_data;
|
||||
CockpitChannel *channel = user_data;
|
||||
JsonObject *options;
|
||||
gint status;
|
||||
gchar *signal;
|
||||
|
||||
process_pipe_buffer (self, NULL);
|
||||
|
||||
self->open = FALSE;
|
||||
|
||||
if (cockpit_pipe_get_pid (pipe, NULL))
|
||||
{
|
||||
options = cockpit_channel_close_options (channel);
|
||||
status = cockpit_pipe_exit_status (pipe);
|
||||
if (WIFEXITED (status))
|
||||
{
|
||||
json_object_set_int_member (options, "exit-status", WEXITSTATUS (status));
|
||||
}
|
||||
else if (WIFSIGNALED (status))
|
||||
{
|
||||
signal = cockpit_strsignal (WTERMSIG (status));
|
||||
json_object_set_string_member (options, "exit-signal", signal);
|
||||
g_free (signal);
|
||||
}
|
||||
else if (status)
|
||||
{
|
||||
json_object_set_int_member (options, "exit-status", -1);
|
||||
}
|
||||
}
|
||||
|
||||
return_stderr_message (channel, pipe);
|
||||
|
||||
/*
|
||||
* In theory we should plumb done handling all the way through to CockpitPipe.
|
||||
* But we can do that later in a compatible way.
|
||||
*/
|
||||
if (problem == NULL)
|
||||
cockpit_channel_control (channel, "done", NULL);
|
||||
|
||||
cockpit_channel_close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_pipe_channel_init (CockpitPipeChannel *self)
|
||||
{
|
||||
/* Has no effect until batch is set */
|
||||
self->latency = 75;
|
||||
}
|
||||
|
||||
static gchar **
|
||||
parse_environ (CockpitChannel *channel,
|
||||
JsonObject *options,
|
||||
const gchar *directory)
|
||||
{
|
||||
const gchar **envset = NULL;
|
||||
gchar **env;
|
||||
|
||||
if (!cockpit_json_get_strv (options, "environ", NULL, &envset))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error", "invalid \"environ\" option for stream channel");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
env = cockpit_pipe_get_environ ((const gchar **)envset, directory);
|
||||
g_free (envset);
|
||||
return env;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_pipe_channel_prepare (CockpitChannel *channel)
|
||||
{
|
||||
CockpitPipeChannel *self = COCKPIT_PIPE_CHANNEL (channel);
|
||||
GSocketAddress *address;
|
||||
CockpitPipeFlags flags;
|
||||
JsonObject *options;
|
||||
const gchar **argv = NULL;
|
||||
gchar **env = NULL;
|
||||
const gchar *internal = NULL;
|
||||
const gchar *dir;
|
||||
const gchar *error;
|
||||
gint fd;
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_pipe_channel_parent_class)->prepare (channel);
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
|
||||
if (!cockpit_json_get_strv (options, "spawn", NULL, &argv))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"spawn\" option for stream channel");
|
||||
goto out;
|
||||
}
|
||||
if (!cockpit_json_get_string (options, "internal", NULL, &internal))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"internal\" option for stream channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Support our options in the open message too */
|
||||
cockpit_pipe_channel_control (channel, "options", options);
|
||||
if (self->closing)
|
||||
goto out;
|
||||
|
||||
if (argv)
|
||||
{
|
||||
if (!cockpit_json_get_string (options, "err", NULL, &error))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"err\" options for stream channel");
|
||||
goto out;
|
||||
}
|
||||
|
||||
flags = COCKPIT_PIPE_FLAGS_NONE;
|
||||
if (g_strcmp0 (error, "out") == 0)
|
||||
flags = COCKPIT_PIPE_STDERR_TO_STDOUT;
|
||||
else if (g_strcmp0 (error, "ignore") == 0)
|
||||
flags = COCKPIT_PIPE_STDERR_TO_NULL;
|
||||
else if (g_strcmp0 (error, "message") == 0)
|
||||
flags = COCKPIT_PIPE_STDERR_TO_MEMORY;
|
||||
|
||||
self->name = g_strdup (argv[0]);
|
||||
if (!self->name)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"\"spawn\" array must not be empty");
|
||||
goto out;
|
||||
}
|
||||
if (!cockpit_json_get_string (options, "directory", NULL, &dir))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"directory\" option for stream channel");
|
||||
goto out;
|
||||
}
|
||||
if (!cockpit_json_get_bool (options, "pty", FALSE, &self->pty))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"invalid \"pty\" option for stream channel");
|
||||
goto out;
|
||||
}
|
||||
env = parse_environ (channel, options, dir);
|
||||
if (!env)
|
||||
goto out;
|
||||
if (self->pty)
|
||||
{
|
||||
gushort rows, cols;
|
||||
|
||||
if (!cockpit_pipe_channel_read_window_size_options (options, 24, 80, &rows, &cols))
|
||||
{
|
||||
g_warning ("%s: invalid \"window.rows\" or \"window.cols\" option for stream channel", self->name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
self->pipe = cockpit_pipe_pty ((const gchar **)argv, (const gchar **)env, dir, rows, cols);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->pipe = cockpit_pipe_spawn ((const gchar **)argv, (const gchar **)env, dir, flags);
|
||||
}
|
||||
}
|
||||
else if (internal)
|
||||
{
|
||||
if (!steal_internal_fd (internal, &fd))
|
||||
{
|
||||
cockpit_channel_close (channel, "not-found");
|
||||
goto out;
|
||||
}
|
||||
|
||||
self->pipe = cockpit_pipe_new_user_fd (internal, fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
address = cockpit_connect_parse_address (channel, &self->name);
|
||||
if (!address)
|
||||
goto out;
|
||||
self->pipe = cockpit_pipe_connect (self->name, address);
|
||||
g_object_unref (address);
|
||||
}
|
||||
|
||||
/* Let the channel throttle the pipe's input flow*/
|
||||
cockpit_flow_throttle (COCKPIT_FLOW (self->pipe), COCKPIT_FLOW (self));
|
||||
|
||||
/* Let the pipe throttle the channel peer's output flow */
|
||||
cockpit_flow_throttle (COCKPIT_FLOW (channel), COCKPIT_FLOW (self->pipe));
|
||||
|
||||
self->sig_read = g_signal_connect (self->pipe, "read", G_CALLBACK (on_pipe_read), self);
|
||||
self->sig_close = g_signal_connect (self->pipe, "close", G_CALLBACK (on_pipe_close), self);
|
||||
self->open = TRUE;
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
|
||||
out:
|
||||
g_free (argv);
|
||||
g_strfreev (env);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_pipe_channel_dispose (GObject *object)
|
||||
{
|
||||
CockpitPipeChannel *self = COCKPIT_PIPE_CHANNEL (object);
|
||||
|
||||
if (self->pipe)
|
||||
{
|
||||
if (self->open)
|
||||
cockpit_pipe_close (self->pipe, "terminated");
|
||||
if (self->sig_read)
|
||||
g_signal_handler_disconnect (self->pipe, self->sig_read);
|
||||
if (self->sig_close)
|
||||
g_signal_handler_disconnect (self->pipe, self->sig_close);
|
||||
self->sig_read = self->sig_close = 0;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (cockpit_pipe_channel_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_pipe_channel_finalize (GObject *object)
|
||||
{
|
||||
CockpitPipeChannel *self = COCKPIT_PIPE_CHANNEL (object);
|
||||
|
||||
g_clear_object (&self->sock);
|
||||
g_clear_object (&self->pipe);
|
||||
g_free (self->name);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_pipe_channel_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_pipe_channel_class_init (CockpitPipeChannelClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = cockpit_pipe_channel_dispose;
|
||||
gobject_class->finalize = cockpit_pipe_channel_finalize;
|
||||
|
||||
channel_class->prepare = cockpit_pipe_channel_prepare;
|
||||
channel_class->control = cockpit_pipe_channel_control;
|
||||
channel_class->recv = cockpit_pipe_channel_recv;
|
||||
channel_class->close = cockpit_pipe_channel_close;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_pipe_channel_open:
|
||||
* @transport: the transport to send/receive messages on
|
||||
* @channel_id: the channel id
|
||||
* @unix_path: the UNIX socket path to communicate with
|
||||
*
|
||||
* This function is mainly used by tests. The usual way
|
||||
* to get a #CockpitPipeChannel is via cockpit_channel_open()
|
||||
*
|
||||
* Returns: (transfer full): the new channel
|
||||
*/
|
||||
CockpitChannel *
|
||||
cockpit_pipe_channel_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *unix_path)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
|
||||
g_return_val_if_fail (channel_id != NULL, NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_string_member (options, "unix", unix_path);
|
||||
json_object_set_string_member (options, "payload", "stream");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_PIPE_CHANNEL,
|
||||
"transport", transport,
|
||||
"id", channel_id,
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
return channel;
|
||||
}
|
||||
|
||||
static void
|
||||
internal_fd_free (gpointer data)
|
||||
{
|
||||
gint fd = GPOINTER_TO_INT (data);
|
||||
|
||||
close (fd);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
cockpit_pipe_channel_add_internal_fd (gint fd)
|
||||
{
|
||||
/* We are not multi-threaded. Also don't make this look like normal fd numbers */
|
||||
static guint64 unique = 911111;
|
||||
gboolean inserted;
|
||||
|
||||
gchar *id;
|
||||
|
||||
if (!internal_fds)
|
||||
internal_fds = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, internal_fd_free);
|
||||
|
||||
id = g_strdup_printf ("internal-stream-%" G_GUINT64_FORMAT, unique++);
|
||||
inserted = g_hash_table_replace (internal_fds, id, GINT_TO_POINTER (fd));
|
||||
|
||||
g_assert (inserted);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_pipe_channel_remove_internal_fd (const gchar *id)
|
||||
{
|
||||
if (internal_fds == NULL)
|
||||
return FALSE;
|
||||
|
||||
if (!g_hash_table_remove (internal_fds, id))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_PIPE_CHANNEL_H__
|
||||
#define COCKPIT_PIPE_CHANNEL_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_PIPE_CHANNEL (cockpit_pipe_channel_get_type ())
|
||||
|
||||
GType cockpit_pipe_channel_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitChannel * cockpit_pipe_channel_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *unix_path);
|
||||
|
||||
const gchar * cockpit_pipe_channel_add_internal_fd (gint fd);
|
||||
|
||||
gboolean cockpit_pipe_channel_remove_internal_fd (const gchar *id);
|
||||
|
||||
#endif /* COCKPIT_PIPE_CHANNEL_H__ */
|
||||
|
|
@ -1,477 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2008 Red Hat, Inc.
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* Public License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: David Zeuthen <davidz@redhat.com>
|
||||
* Cockpit Authors
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitpolkitagent.h"
|
||||
#include "cockpitrouter.h"
|
||||
|
||||
#include "common/cockpithex.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpittransport.h"
|
||||
|
||||
#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE 1
|
||||
#include <polkitagent/polkitagent.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
|
||||
#define COCKPIT_TYPE_POLKIT_AGENT (cockpit_polkit_agent_get_type())
|
||||
#define COCKPIT_POLKIT_AGENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_POLKIT_AGENT, CockpitPolkitAgent))
|
||||
#define COCKPIT_IS_POLKIT_AGENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), COCKPIT_TYPE_POLKIT_AGENT))
|
||||
|
||||
typedef struct {
|
||||
PolkitAgentListener parent_instance;
|
||||
CockpitTransport *transport;
|
||||
CockpitRouter *router;
|
||||
|
||||
/* Polkit helper sessions active */
|
||||
GHashTable *callers;
|
||||
} CockpitPolkitAgent;
|
||||
|
||||
typedef struct {
|
||||
gchar *cookie;
|
||||
gchar *user;
|
||||
|
||||
GTask *result;
|
||||
CockpitPolkitAgent *self;
|
||||
|
||||
PolkitAgentSession *session;
|
||||
gulong completed_sig;
|
||||
gulong request_sig;
|
||||
gulong info_sig;
|
||||
gulong error_sig;
|
||||
|
||||
GCancellable *cancellable;
|
||||
gulong cancel_sig;
|
||||
} ReauthorizeCaller;
|
||||
|
||||
typedef struct {
|
||||
PolkitAgentListenerClass parent_class;
|
||||
} CockpitPolkitAgentClass;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_TRANSPORT,
|
||||
PROP_ROUTER,
|
||||
};
|
||||
|
||||
static GType cockpit_polkit_agent_get_type (void) G_GNUC_CONST;
|
||||
|
||||
G_DEFINE_TYPE (CockpitPolkitAgent, cockpit_polkit_agent, POLKIT_AGENT_TYPE_LISTENER);
|
||||
|
||||
static void
|
||||
caller_free (gpointer data)
|
||||
{
|
||||
ReauthorizeCaller *caller = data;
|
||||
if (caller->cancel_sig)
|
||||
g_signal_handler_disconnect (caller->cancellable, caller->cancel_sig);
|
||||
if (caller->cancellable)
|
||||
g_object_unref (caller->cancellable);
|
||||
|
||||
if (caller->session)
|
||||
{
|
||||
g_signal_handler_disconnect (caller->session, caller->completed_sig);
|
||||
g_signal_handler_disconnect (caller->session, caller->request_sig);
|
||||
g_signal_handler_disconnect (caller->session, caller->info_sig);
|
||||
g_signal_handler_disconnect (caller->session, caller->error_sig);
|
||||
polkit_agent_session_cancel (caller->session);
|
||||
g_object_unref (caller->session);
|
||||
}
|
||||
|
||||
if (caller->result)
|
||||
{
|
||||
g_debug ("cancelling agent authentication");
|
||||
g_task_return_new_error (caller->result, G_IO_ERROR, G_IO_ERROR_CANCELLED,
|
||||
"Operation was cancelled");
|
||||
g_object_unref (caller->result);
|
||||
}
|
||||
|
||||
if (caller->self->router)
|
||||
cockpit_router_prompt_cancel (caller->self->router, caller);
|
||||
|
||||
g_free (caller->cookie);
|
||||
g_free (caller->user);
|
||||
g_free (caller);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_polkit_agent_init (CockpitPolkitAgent *self)
|
||||
{
|
||||
self->callers = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, caller_free);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_polkit_agent_constructed (GObject *object)
|
||||
{
|
||||
G_OBJECT_CLASS (cockpit_polkit_agent_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_polkit_agent_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CockpitPolkitAgent *self = COCKPIT_POLKIT_AGENT (object);
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_TRANSPORT:
|
||||
self->transport = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_ROUTER:
|
||||
self->router = g_value_get_object (value);
|
||||
if (self->router)
|
||||
g_object_add_weak_pointer (G_OBJECT(self->router), (gpointer *)&self->router);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_polkit_agent_dispose (GObject *object)
|
||||
{
|
||||
CockpitPolkitAgent *self = COCKPIT_POLKIT_AGENT (object);
|
||||
|
||||
g_hash_table_remove_all (self->callers);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_polkit_agent_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_polkit_agent_finalize (GObject *object)
|
||||
{
|
||||
CockpitPolkitAgent *self = COCKPIT_POLKIT_AGENT (object);
|
||||
|
||||
if (self->transport)
|
||||
g_object_unref (self->transport);
|
||||
if (self->router)
|
||||
g_object_remove_weak_pointer (G_OBJECT(self->router), (gpointer *)&self->router);
|
||||
|
||||
g_hash_table_destroy (self->callers);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_polkit_agent_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
on_completed (PolkitAgentSession *session,
|
||||
gboolean gained_authorization,
|
||||
gpointer user_data)
|
||||
{
|
||||
ReauthorizeCaller *caller = user_data;
|
||||
|
||||
g_debug ("polkit authentication completed");
|
||||
|
||||
g_warn_if_fail (g_hash_table_steal (caller->self->callers, caller->cookie));
|
||||
g_task_return_boolean (caller->result, TRUE);
|
||||
|
||||
g_object_unref (caller->result);
|
||||
caller->result = NULL;
|
||||
|
||||
caller_free (caller);
|
||||
}
|
||||
|
||||
static void
|
||||
on_answer (const gchar *value,
|
||||
gpointer user_data)
|
||||
{
|
||||
ReauthorizeCaller *caller = user_data;
|
||||
polkit_agent_session_response (caller->session, value ? value : "");
|
||||
}
|
||||
|
||||
static void
|
||||
on_request (PolkitAgentSession *session,
|
||||
const gchar *request,
|
||||
gboolean echo_on,
|
||||
gpointer user_data)
|
||||
{
|
||||
ReauthorizeCaller *caller = user_data;
|
||||
|
||||
if (echo_on)
|
||||
{
|
||||
g_message ("ignoring polkit helper request: %s", request);
|
||||
polkit_agent_session_response (session, "");
|
||||
}
|
||||
else if (caller->self->router)
|
||||
cockpit_router_prompt (caller->self->router, caller->user, NULL, NULL, on_answer, caller);
|
||||
}
|
||||
|
||||
static void
|
||||
on_show_error (PolkitAgentSession *session,
|
||||
const gchar *text,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_message ("polkit helper error: %s", text);
|
||||
}
|
||||
|
||||
static void
|
||||
on_show_info (PolkitAgentSession *session,
|
||||
const gchar *text,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_message ("polkit helper info: %s", text);
|
||||
}
|
||||
|
||||
static void
|
||||
on_cancelled (GCancellable *cancellable,
|
||||
gpointer user_data)
|
||||
{
|
||||
ReauthorizeCaller *caller = user_data;
|
||||
g_debug ("cancelled agent authentication");
|
||||
g_hash_table_remove (caller->self->callers, caller->cookie);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_polkit_agent_initiate_authentication (PolkitAgentListener *listener,
|
||||
const gchar *action_id,
|
||||
const gchar *message,
|
||||
const gchar *icon_name,
|
||||
PolkitDetails *details,
|
||||
const gchar *cookie,
|
||||
GList *identities,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitPolkitAgent *self = COCKPIT_POLKIT_AGENT (listener);
|
||||
PolkitIdentity *identity = NULL;
|
||||
GTask *result = NULL;
|
||||
GString *unsupported = NULL;
|
||||
ReauthorizeCaller *caller;
|
||||
const gchar *name = NULL;
|
||||
gchar *string;
|
||||
uid_t uid;
|
||||
GList *l;
|
||||
|
||||
g_debug ("polkit is requesting authentication");
|
||||
|
||||
result = g_task_new (G_OBJECT (self), NULL, callback, user_data);
|
||||
|
||||
if (!g_str_equal (action_id, "org.cockpit-project.cockpit.root-bridge"))
|
||||
{
|
||||
g_task_return_new_error (result, POLKIT_ERROR, POLKIT_ERROR_FAILED,
|
||||
"Unsupported action");
|
||||
goto out;
|
||||
}
|
||||
|
||||
uid = getuid ();
|
||||
|
||||
unsupported = g_string_new ("");
|
||||
for (l = identities; l != NULL; l = g_list_next (l))
|
||||
{
|
||||
if (POLKIT_IS_UNIX_USER (l->data))
|
||||
{
|
||||
if (polkit_unix_user_get_uid (l->data) == uid)
|
||||
{
|
||||
identity = g_object_ref (l->data);
|
||||
name = polkit_unix_user_get_name (l->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string = polkit_identity_to_string (l->data);
|
||||
g_string_append_printf (unsupported, "%s ", string);
|
||||
g_free (string);
|
||||
}
|
||||
|
||||
if (!identity)
|
||||
{
|
||||
g_message ("cannot reauthorize identity(s): %s", unsupported->str);
|
||||
g_task_return_new_error (result, POLKIT_ERROR, POLKIT_ERROR_FAILED,
|
||||
"Reauthorization not supported for identity");
|
||||
goto out;
|
||||
}
|
||||
|
||||
caller = g_new0 (ReauthorizeCaller, 1);
|
||||
caller->cookie = g_strdup (cookie);
|
||||
caller->user = g_strdup (name);
|
||||
caller->session = polkit_agent_session_new (identity, cookie);
|
||||
caller->completed_sig = g_signal_connect (caller->session, "completed", G_CALLBACK (on_completed), caller);
|
||||
caller->request_sig = g_signal_connect (caller->session, "request", G_CALLBACK (on_request), caller);
|
||||
caller->info_sig = g_signal_connect (caller->session, "show-info", G_CALLBACK (on_show_info), NULL);
|
||||
caller->error_sig = g_signal_connect (caller->session, "show-error", G_CALLBACK (on_show_error), NULL);
|
||||
caller->cancellable = g_object_ref (cancellable);
|
||||
caller->cancel_sig = g_cancellable_connect (cancellable, G_CALLBACK (on_cancelled), caller, NULL);
|
||||
|
||||
caller->result = g_object_ref (result);
|
||||
caller->self = self;
|
||||
|
||||
polkit_agent_session_initiate (caller->session);
|
||||
g_hash_table_replace (self->callers, caller->cookie, caller);
|
||||
|
||||
g_debug ("polkit helper starting");
|
||||
|
||||
out:
|
||||
if (unsupported)
|
||||
g_string_free (unsupported, TRUE);
|
||||
g_object_unref (result);
|
||||
if (identity)
|
||||
g_object_unref (identity);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cockpit_polkit_agent_initiate_authentication_finish (PolkitAgentListener *listener,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
g_warn_if_fail (g_task_is_valid (res, listener));
|
||||
|
||||
return g_task_propagate_boolean (G_TASK (res), error);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_polkit_agent_class_init (CockpitPolkitAgentClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
PolkitAgentListenerClass *listener_class = POLKIT_AGENT_LISTENER_CLASS (klass);
|
||||
|
||||
gobject_class->constructed = cockpit_polkit_agent_constructed;
|
||||
gobject_class->set_property = cockpit_polkit_agent_set_property;
|
||||
gobject_class->dispose = cockpit_polkit_agent_dispose;
|
||||
gobject_class->finalize = cockpit_polkit_agent_finalize;
|
||||
|
||||
listener_class->initiate_authentication = cockpit_polkit_agent_initiate_authentication;
|
||||
listener_class->initiate_authentication_finish = cockpit_polkit_agent_initiate_authentication_finish;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_TRANSPORT,
|
||||
g_param_spec_object ("transport", "transport", "transport", COCKPIT_TYPE_TRANSPORT,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_ROUTER,
|
||||
g_param_spec_object ("router", "router", "router", COCKPIT_TYPE_ROUTER,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
PolkitAgentListener *listener;
|
||||
gpointer registration_handle;
|
||||
} CockpitPolkitRegistered;
|
||||
|
||||
gpointer
|
||||
cockpit_polkit_agent_register (CockpitTransport *transport,
|
||||
CockpitRouter *router,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
CockpitPolkitRegistered *registered;
|
||||
PolkitAgentListener *listener = NULL;
|
||||
PolkitAuthority *authority = NULL;
|
||||
PolkitSubject *subject = NULL;
|
||||
GVariant *options;
|
||||
GError *error = NULL;
|
||||
gpointer handle = NULL;
|
||||
gchar *string;
|
||||
|
||||
g_return_val_if_fail (transport != NULL, NULL);
|
||||
|
||||
authority = polkit_authority_get_sync (cancellable, &error);
|
||||
if (authority == NULL)
|
||||
{
|
||||
g_message ("couldn't get polkit authority: %s", error->message);
|
||||
goto out;
|
||||
}
|
||||
|
||||
subject = polkit_unix_session_new_for_process_sync (getpid (), cancellable, &error);
|
||||
if (subject == NULL)
|
||||
{
|
||||
/*
|
||||
* This can happen if there's a race between the polkit request and closing of
|
||||
* Cockpit. So it's not unheard of. We can complain, but not too loudly.
|
||||
*/
|
||||
g_message ("couldn't create polkit session subject: %s", error->message);
|
||||
goto out;
|
||||
}
|
||||
|
||||
listener = g_object_new (COCKPIT_TYPE_POLKIT_AGENT, "transport", transport, "router", router, NULL);
|
||||
options = NULL;
|
||||
|
||||
handle = polkit_agent_listener_register_with_options (listener,
|
||||
POLKIT_AGENT_REGISTER_FLAGS_NONE,
|
||||
subject, NULL, options, cancellable, &error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
if ((g_error_matches (error, POLKIT_ERROR, POLKIT_ERROR_FAILED) &&
|
||||
error->message && strstr (error->message, "already exists")) ||
|
||||
g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
|
||||
{
|
||||
g_debug ("couldn't register polkit agent: %s", error->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_dbus_error_strip_remote_error (error);
|
||||
g_message ("couldn't register polkit authentication agent: %s", error->message);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
string = polkit_subject_to_string (subject);
|
||||
|
||||
g_debug ("registered polkit authentication agent for subject: %s", string);
|
||||
g_free (string);
|
||||
|
||||
out:
|
||||
if (subject)
|
||||
g_object_unref (subject);
|
||||
if (authority)
|
||||
g_object_unref (authority);
|
||||
g_clear_error (&error);
|
||||
|
||||
if (handle)
|
||||
{
|
||||
registered = g_new0 (CockpitPolkitRegistered, 1);
|
||||
registered->registration_handle = handle;
|
||||
registered->listener = listener;
|
||||
return registered;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (listener)
|
||||
g_object_unref (listener);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_polkit_agent_unregister (gpointer data)
|
||||
{
|
||||
CockpitPolkitRegistered *registered = data;
|
||||
|
||||
if (!registered)
|
||||
return;
|
||||
|
||||
/* Explicitly cancel all pending operations */
|
||||
g_object_run_dispose (G_OBJECT (registered->listener));
|
||||
g_object_unref (registered->listener);
|
||||
|
||||
/* Now unregister with polkit */
|
||||
polkit_agent_listener_unregister (registered->registration_handle);
|
||||
|
||||
g_free (registered);
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2008 Red Hat, Inc.
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* Public License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: David Zeuthen <davidz@redhat.com>
|
||||
* Cockpit Authors
|
||||
*/
|
||||
|
||||
#ifndef __COCKPIT_POLKIT_AGENT_H
|
||||
#define __COCKPIT_POLKIT_AGENT_H
|
||||
|
||||
#include "common/cockpittransport.h"
|
||||
#include "cockpitrouter.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gpointer cockpit_polkit_agent_register (CockpitTransport *transport,
|
||||
CockpitRouter *router,
|
||||
GCancellable *cancellable);
|
||||
|
||||
void cockpit_polkit_agent_unregister (gpointer handle);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __COCKPIT_POLKIT_AGENT_H */
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __COCKPIT_ROUTER_H__
|
||||
#define __COCKPIT_ROUTER_H__
|
||||
|
||||
#include "common/cockpittransport.h"
|
||||
#include "cockpitpeer.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct {
|
||||
const gchar *name;
|
||||
GType (* function) (void);
|
||||
} CockpitPayloadType;
|
||||
|
||||
#define COCKPIT_TYPE_ROUTER (cockpit_router_get_type ())
|
||||
#define COCKPIT_ROUTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_ROUTER, CockpitRouter))
|
||||
#define COCKPIT_IS_ROUTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), COCKPIT_TYPE_ROUTER))
|
||||
|
||||
typedef struct _CockpitRouter CockpitRouter;
|
||||
|
||||
typedef void CockpitRouterPromptAnswerFunction (const gchar *value,
|
||||
gpointer data);
|
||||
|
||||
GType cockpit_router_get_type (void) G_GNUC_CONST;
|
||||
|
||||
CockpitRouter * cockpit_router_new (CockpitTransport *transport,
|
||||
CockpitPayloadType *payloads,
|
||||
GList *bridges);
|
||||
|
||||
void cockpit_router_add_channel (CockpitRouter *self,
|
||||
JsonObject *match,
|
||||
GType (* function) (void));
|
||||
|
||||
void cockpit_router_add_bridge (CockpitRouter *self,
|
||||
JsonObject *config);
|
||||
|
||||
void cockpit_router_add_peer (CockpitRouter *self,
|
||||
JsonObject *match,
|
||||
CockpitPeer *peer);
|
||||
void cockpit_router_set_bridges (CockpitRouter *self,
|
||||
GList *bridge_configs);
|
||||
|
||||
void cockpit_router_dump_rules (CockpitRouter *self);
|
||||
|
||||
void cockpit_router_dbus_startup (CockpitRouter *self);
|
||||
|
||||
void cockpit_router_prompt (CockpitRouter *self,
|
||||
const gchar *user,
|
||||
const gchar *prompt,
|
||||
const gchar *previous_error,
|
||||
CockpitRouterPromptAnswerFunction *answer,
|
||||
gpointer data);
|
||||
|
||||
void cockpit_router_prompt_cancel (CockpitRouter *self,
|
||||
gpointer data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __COCKPIT_ROUTER_H__ */
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitsamples.h"
|
||||
|
||||
G_DEFINE_INTERFACE (CockpitSamples, cockpit_samples, 0);
|
||||
|
||||
static void
|
||||
cockpit_samples_default_init (CockpitSamplesInterface *iface)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_samples_sample (CockpitSamples *self,
|
||||
const gchar *metric,
|
||||
const gchar *instance,
|
||||
gint64 value)
|
||||
{
|
||||
CockpitSamplesInterface *iface;
|
||||
|
||||
iface = COCKPIT_SAMPLES_GET_IFACE (self);
|
||||
g_return_if_fail (iface != NULL);
|
||||
|
||||
g_assert (iface->sample);
|
||||
(iface->sample) (self, metric, instance, value);
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COCKPIT_SAMPLES_H__
|
||||
#define COCKPIT_SAMPLES_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define COCKPIT_TYPE_SAMPLES (cockpit_samples_get_type ())
|
||||
#define COCKPIT_SAMPLES(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), COCKPIT_TYPE_SAMPLES, CockpitSamples))
|
||||
#define COCKPIT_IS_SAMPLES(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), COCKPIT_TYPE_SAMPLES))
|
||||
#define COCKPIT_SAMPLES_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), COCKPIT_TYPE_SAMPLES, CockpitSamplesInterface))
|
||||
|
||||
typedef struct _CockpitSamples CockpitSamples;
|
||||
typedef struct _CockpitSamplesInterface CockpitSamplesInterface;
|
||||
|
||||
struct _CockpitSamplesInterface {
|
||||
GTypeInterface parent_iface;
|
||||
|
||||
void (* sample) (CockpitSamples *samples,
|
||||
const gchar *metric,
|
||||
const gchar *instance,
|
||||
gint64 value);
|
||||
};
|
||||
|
||||
GType cockpit_samples_get_type (void) G_GNUC_CONST;
|
||||
|
||||
void cockpit_samples_sample (CockpitSamples *self,
|
||||
const gchar *metric,
|
||||
const gchar *instance,
|
||||
gint64 value);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* COCKPIT_SAMPLES_H__ */
|
||||
|
|
@ -1,950 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitstream.h"
|
||||
|
||||
#include "common/cockpitflow.h"
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* CockpitStream:
|
||||
*
|
||||
* A stream with queued input and output based on top of a GIOStream
|
||||
*
|
||||
* This stream can do flow control in two ways:
|
||||
*
|
||||
* - Its input can be throttled, it can listen to a "pressure" signal
|
||||
* from another object passed into cockpit_stream_throttle()
|
||||
* - It can optionally control another flow, by emitting a "pressure" signal
|
||||
* when its output queue is too large
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_NAME,
|
||||
PROP_IO_STREAM,
|
||||
PROP_PROBLEM
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
gchar *name;
|
||||
GMainContext *context;
|
||||
|
||||
gboolean closed;
|
||||
gboolean closing;
|
||||
CockpitConnectable *connecting;
|
||||
gchar *problem;
|
||||
|
||||
GIOStream *io;
|
||||
|
||||
GSource *out_source;
|
||||
GQueue *out_queue;
|
||||
gsize out_queued;
|
||||
gsize out_partial;
|
||||
gboolean out_closed;
|
||||
|
||||
gboolean in_done;
|
||||
GSource *in_source;
|
||||
GByteArray *in_buffer;
|
||||
gboolean received;
|
||||
|
||||
/* Throttle this flow based on back pressure from another object */
|
||||
CockpitFlow *pressure;
|
||||
gulong pressure_sig;
|
||||
} CockpitStreamPrivate;
|
||||
|
||||
/* A megabyte is when we start to consider queue full enough */
|
||||
#define QUEUE_PRESSURE 1024UL * 1024UL
|
||||
|
||||
static guint cockpit_stream_sig_open;
|
||||
static guint cockpit_stream_sig_read;
|
||||
static guint cockpit_stream_sig_close;
|
||||
|
||||
static void cockpit_close_later (CockpitStream *self);
|
||||
|
||||
static void cockpit_stream_flow_iface_init (CockpitFlowInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CockpitStream, cockpit_stream, G_TYPE_OBJECT,
|
||||
G_ADD_PRIVATE(CockpitStream)
|
||||
G_IMPLEMENT_INTERFACE (COCKPIT_TYPE_FLOW, cockpit_stream_flow_iface_init));
|
||||
|
||||
#define GET_PRIV(self) ((CockpitStreamPrivate *) cockpit_stream_get_instance_private(self))
|
||||
|
||||
static void
|
||||
cockpit_stream_init (CockpitStream *self)
|
||||
{
|
||||
GET_PRIV(self)->in_buffer = g_byte_array_new ();
|
||||
GET_PRIV(self)->out_queue = g_queue_new ();
|
||||
|
||||
GET_PRIV(self)->context = g_main_context_ref_thread_default ();
|
||||
}
|
||||
|
||||
static void
|
||||
stop_output (CockpitStream *self)
|
||||
{
|
||||
g_assert (GET_PRIV(self)->out_source != NULL);
|
||||
g_source_destroy (GET_PRIV(self)->out_source);
|
||||
g_source_unref (GET_PRIV(self)->out_source);
|
||||
GET_PRIV(self)->out_source = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
stop_input (CockpitStream *self)
|
||||
{
|
||||
g_assert (GET_PRIV(self)->in_source != NULL);
|
||||
g_source_destroy (GET_PRIV(self)->in_source);
|
||||
g_source_unref (GET_PRIV(self)->in_source);
|
||||
GET_PRIV(self)->in_source = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
close_immediately (CockpitStream *self,
|
||||
const gchar *problem)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GIOStream *io;
|
||||
|
||||
if (GET_PRIV(self)->closed)
|
||||
return;
|
||||
|
||||
if (problem)
|
||||
{
|
||||
g_free (GET_PRIV(self)->problem);
|
||||
GET_PRIV(self)->problem = g_strdup (problem);
|
||||
}
|
||||
|
||||
if (GET_PRIV(self)->connecting)
|
||||
{
|
||||
cockpit_connectable_unref (GET_PRIV(self)->connecting);
|
||||
GET_PRIV(self)->connecting = NULL;
|
||||
}
|
||||
|
||||
GET_PRIV(self)->closed = TRUE;
|
||||
|
||||
g_debug ("%s: closing stream%s%s", GET_PRIV(self)->name,
|
||||
GET_PRIV(self)->problem ? ": " : "",
|
||||
GET_PRIV(self)->problem ? GET_PRIV(self)->problem : "");
|
||||
|
||||
if (GET_PRIV(self)->in_source)
|
||||
stop_input (self);
|
||||
if (GET_PRIV(self)->out_source)
|
||||
stop_output (self);
|
||||
|
||||
if (GET_PRIV(self)->io)
|
||||
{
|
||||
io = GET_PRIV(self)->io;
|
||||
GET_PRIV(self)->io = NULL;
|
||||
|
||||
g_io_stream_close (io, NULL, &error);
|
||||
if (error)
|
||||
{
|
||||
g_message ("%s: close failed: %s", GET_PRIV(self)->name, error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
g_object_unref (io);
|
||||
}
|
||||
|
||||
g_debug ("%s: closed", GET_PRIV(self)->name);
|
||||
g_signal_emit (self, cockpit_stream_sig_close, 0, GET_PRIV(self)->problem);
|
||||
}
|
||||
|
||||
static void
|
||||
close_maybe (CockpitStream *self)
|
||||
{
|
||||
if (!GET_PRIV(self)->closed)
|
||||
{
|
||||
if (GET_PRIV(self)->in_done && GET_PRIV(self)->out_closed)
|
||||
{
|
||||
g_debug ("%s: input and output done", GET_PRIV(self)->name);
|
||||
close_immediately (self, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_output_closed (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (user_data);
|
||||
GError *error = NULL;
|
||||
|
||||
GET_PRIV(self)->out_closed = TRUE;
|
||||
|
||||
g_output_stream_close_finish (G_OUTPUT_STREAM (object), result, &error);
|
||||
if (error)
|
||||
{
|
||||
g_warning ("%s: couldn't close output stream: %s", GET_PRIV(self)->name, error->message);
|
||||
close_immediately (self, "internal-error");
|
||||
}
|
||||
|
||||
close_maybe (self);
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
static void
|
||||
close_output (CockpitStream *self)
|
||||
{
|
||||
if (GET_PRIV(self)->out_closed)
|
||||
return;
|
||||
|
||||
g_debug ("%s: end of output", GET_PRIV(self)->name);
|
||||
|
||||
if (!GET_PRIV(self)->io)
|
||||
{
|
||||
close_maybe (self);
|
||||
return;
|
||||
}
|
||||
|
||||
g_output_stream_close_async (g_io_stream_get_output_stream (GET_PRIV(self)->io),
|
||||
G_PRIORITY_DEFAULT, NULL, on_output_closed, g_object_ref (self));
|
||||
}
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2,43,2)
|
||||
#define G_IO_ERROR_CONNECTION_CLOSED G_IO_ERROR_BROKEN_PIPE
|
||||
#endif
|
||||
|
||||
const gchar *
|
||||
cockpit_stream_problem (GError *error,
|
||||
const gchar *name,
|
||||
const gchar *summary,
|
||||
JsonObject *options)
|
||||
{
|
||||
const gchar *problem = NULL;
|
||||
gchar *message;
|
||||
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
|
||||
problem = "access-denied";
|
||||
else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) ||
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) ||
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE) ||
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE) ||
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND) ||
|
||||
g_error_matches (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND))
|
||||
problem = "not-found";
|
||||
else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) ||
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED) ||
|
||||
g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_EOF))
|
||||
problem = "disconnected";
|
||||
#if !GLIB_CHECK_VERSION(2,43,2)
|
||||
else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED) &&
|
||||
strstr (error->message, g_strerror (ECONNRESET)))
|
||||
problem = "disconnected";
|
||||
#endif
|
||||
else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
|
||||
problem = "timeout";
|
||||
else if (g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS) ||
|
||||
g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_MISC))
|
||||
problem = "protocol-error";
|
||||
else if (g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE))
|
||||
{
|
||||
problem = "unknown-hostkey";
|
||||
}
|
||||
|
||||
if (problem)
|
||||
{
|
||||
message = g_strdup_printf ("%s: %s: %s", name, summary, error->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = g_strdup_printf ("%s: %s: %s", name, summary, error->message);
|
||||
problem = "internal-error";
|
||||
}
|
||||
if (options)
|
||||
{
|
||||
if (!json_object_has_member (options, "message"))
|
||||
json_object_set_string_member (options, "message", message);
|
||||
}
|
||||
g_message ("%s", message);
|
||||
g_free (message);
|
||||
|
||||
return problem;
|
||||
}
|
||||
|
||||
static void
|
||||
set_problem_from_error (CockpitStream *self,
|
||||
const gchar *summary,
|
||||
GError *error)
|
||||
{
|
||||
const gchar *problem;
|
||||
|
||||
if (g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_MISC))
|
||||
{
|
||||
g_message ("%s: %s: %s", GET_PRIV(self)->name, summary, error->message);
|
||||
if (GET_PRIV(self)->received)
|
||||
problem = "disconnected";
|
||||
else
|
||||
problem = "protocol-error";
|
||||
}
|
||||
else
|
||||
{
|
||||
problem = cockpit_stream_problem (error, GET_PRIV(self)->name, summary, NULL);
|
||||
}
|
||||
|
||||
g_free (GET_PRIV(self)->problem);
|
||||
GET_PRIV(self)->problem = g_strdup (problem);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dispatch_input (GPollableInputStream *is,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitStream *self = (CockpitStream *)user_data;
|
||||
GError *error = NULL;
|
||||
gboolean read = FALSE;
|
||||
gssize ret = 0;
|
||||
gsize len;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
g_return_val_if_fail (GET_PRIV(self)->in_source, FALSE);
|
||||
len = GET_PRIV(self)->in_buffer->len;
|
||||
|
||||
g_byte_array_set_size (GET_PRIV(self)->in_buffer, len + 1024);
|
||||
ret = g_pollable_input_stream_read_nonblocking (is, GET_PRIV(self)->in_buffer->data + len,
|
||||
1024, NULL, &error);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
g_byte_array_set_size (GET_PRIV(self)->in_buffer, len);
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|
||||
{
|
||||
g_error_free (error);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_problem_from_error (self, "couldn't read", error);
|
||||
g_error_free (error);
|
||||
close_immediately (self, NULL);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
g_byte_array_set_size (GET_PRIV(self)->in_buffer, len + ret);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
g_debug ("%s: end of input", GET_PRIV(self)->name);
|
||||
GET_PRIV(self)->in_done = TRUE;
|
||||
stop_input (self);
|
||||
break;
|
||||
}
|
||||
|
||||
g_debug ("%s: read %d bytes", GET_PRIV(self)->name, (int)ret);
|
||||
GET_PRIV(self)->received = TRUE;
|
||||
read = TRUE;
|
||||
}
|
||||
|
||||
g_object_ref (self);
|
||||
|
||||
if (GET_PRIV(self)->in_done || read)
|
||||
g_signal_emit (self, cockpit_stream_sig_read, 0, GET_PRIV(self)->in_buffer, GET_PRIV(self)->in_done);
|
||||
|
||||
if (GET_PRIV(self)->in_done)
|
||||
close_maybe (self);
|
||||
|
||||
g_object_unref (self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dispatch_output (GPollableOutputStream *os,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitStream *self = (CockpitStream *)user_data;
|
||||
GError *error = NULL;
|
||||
const gint8 *data;
|
||||
gsize len, size, before;
|
||||
GBytes *popped;
|
||||
gssize ret;
|
||||
|
||||
g_return_val_if_fail (GET_PRIV(self)->out_source, FALSE);
|
||||
while (GET_PRIV(self)->out_queue->head)
|
||||
{
|
||||
data = g_bytes_get_data (GET_PRIV(self)->out_queue->head->data, &len);
|
||||
g_assert (GET_PRIV(self)->out_partial <= len);
|
||||
|
||||
ret = g_pollable_output_stream_write_nonblocking (os, data + GET_PRIV(self)->out_partial,
|
||||
len - GET_PRIV(self)->out_partial, NULL, &error);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|
||||
{
|
||||
g_debug ("%s: output would block", GET_PRIV(self)->name);
|
||||
g_error_free (error);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_problem_from_error (self, "couldn't write", error);
|
||||
g_error_free (error);
|
||||
close_immediately (self, NULL);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
GET_PRIV(self)->out_partial += ret;
|
||||
if (GET_PRIV(self)->out_partial >= len)
|
||||
{
|
||||
before = GET_PRIV(self)->out_queued;
|
||||
|
||||
g_debug ("%s: wrote %d bytes", GET_PRIV(self)->name, (int)len);
|
||||
popped = g_queue_pop_head (GET_PRIV(self)->out_queue);
|
||||
size = g_bytes_get_size (popped);
|
||||
g_assert (size <= GET_PRIV(self)->out_queued);
|
||||
GET_PRIV(self)->out_queued -= size;
|
||||
g_bytes_unref (popped);
|
||||
GET_PRIV(self)->out_partial = 0;
|
||||
|
||||
if (before >= QUEUE_PRESSURE && GET_PRIV(self)->out_queued < QUEUE_PRESSURE)
|
||||
cockpit_flow_emit_pressure (COCKPIT_FLOW (self), FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ret > 0)
|
||||
g_debug ("%s: partial write %d of %d bytes", GET_PRIV(self)->name, (int)ret, (int)len);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
g_debug ("%s: output queue empty", GET_PRIV(self)->name);
|
||||
|
||||
/* If all messages are done, then stop polling out fd */
|
||||
stop_output (self);
|
||||
|
||||
if (GET_PRIV(self)->closing)
|
||||
close_output (self);
|
||||
else
|
||||
close_maybe (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
start_output (CockpitStream *self)
|
||||
{
|
||||
GOutputStream *os;
|
||||
|
||||
g_assert (GET_PRIV(self)->out_source == NULL);
|
||||
|
||||
if (GET_PRIV(self)->connecting || GET_PRIV(self)->out_closed || GET_PRIV(self)->closed)
|
||||
return;
|
||||
|
||||
g_assert (GET_PRIV(self)->io);
|
||||
|
||||
os = g_io_stream_get_output_stream (GET_PRIV(self)->io);
|
||||
GET_PRIV(self)->out_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (os), NULL);
|
||||
g_source_set_name (GET_PRIV(self)->out_source, "stream-output");
|
||||
g_source_set_callback (GET_PRIV(self)->out_source, (GSourceFunc)dispatch_output, self, NULL);
|
||||
g_source_attach (GET_PRIV(self)->out_source, GET_PRIV(self)->context);
|
||||
}
|
||||
|
||||
static void
|
||||
start_input (CockpitStream *self)
|
||||
{
|
||||
GInputStream *is;
|
||||
|
||||
g_assert (GET_PRIV(self)->in_source == NULL);
|
||||
g_assert (!GET_PRIV(self)->connecting);
|
||||
g_assert (GET_PRIV(self)->io != NULL);
|
||||
|
||||
is = g_io_stream_get_input_stream (GET_PRIV(self)->io);
|
||||
GET_PRIV(self)->in_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (is), NULL);
|
||||
g_source_set_name (GET_PRIV(self)->in_source, "stream-input");
|
||||
g_source_set_callback (GET_PRIV(self)->in_source, (GSourceFunc)dispatch_input, self, NULL);
|
||||
g_source_attach (GET_PRIV(self)->in_source, GET_PRIV(self)->context);
|
||||
}
|
||||
|
||||
static void
|
||||
initialize_io (CockpitStream *self)
|
||||
{
|
||||
GInputStream *is;
|
||||
GOutputStream *os;
|
||||
|
||||
g_return_if_fail (GET_PRIV(self)->in_source == NULL);
|
||||
|
||||
is = g_io_stream_get_input_stream (GET_PRIV(self)->io);
|
||||
os = g_io_stream_get_output_stream (GET_PRIV(self)->io);
|
||||
|
||||
if (!G_IS_POLLABLE_INPUT_STREAM (is) ||
|
||||
!g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (is)) ||
|
||||
!G_IS_POLLABLE_OUTPUT_STREAM (os) ||
|
||||
!g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (os)))
|
||||
{
|
||||
g_warning ("%s: stream is not pollable", GET_PRIV(self)->name);
|
||||
close_immediately (self, "internal-error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GET_PRIV(self)->connecting)
|
||||
{
|
||||
cockpit_connectable_unref (GET_PRIV(self)->connecting);
|
||||
GET_PRIV(self)->connecting = NULL;
|
||||
}
|
||||
|
||||
start_input (self);
|
||||
|
||||
start_output (self);
|
||||
|
||||
g_signal_emit (self, cockpit_stream_sig_open, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_stream_constructed (GObject *object)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (object);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_stream_parent_class)->constructed (object);
|
||||
|
||||
if (GET_PRIV(self)->io)
|
||||
initialize_io (self);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_stream_set_property (GObject *obj,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (obj);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_NAME:
|
||||
GET_PRIV(self)->name = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_IO_STREAM:
|
||||
GET_PRIV(self)->io = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_PROBLEM:
|
||||
GET_PRIV(self)->problem = g_value_dup_string (value);
|
||||
if (GET_PRIV(self)->problem)
|
||||
cockpit_close_later (self);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_stream_get_property (GObject *obj,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (obj);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_NAME:
|
||||
g_value_set_string (value, GET_PRIV(self)->name);
|
||||
break;
|
||||
case PROP_IO_STREAM:
|
||||
g_value_set_object (value, GET_PRIV(self)->io);
|
||||
break;
|
||||
case PROP_PROBLEM:
|
||||
g_value_set_string (value, GET_PRIV(self)->problem);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_stream_dispose (GObject *object)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (object);
|
||||
|
||||
if (!GET_PRIV(self)->closed)
|
||||
close_immediately (self, "terminated");
|
||||
|
||||
cockpit_flow_throttle (COCKPIT_FLOW (self), NULL);
|
||||
g_assert (GET_PRIV(self)->pressure == NULL);
|
||||
|
||||
while (GET_PRIV(self)->out_queue->head)
|
||||
g_bytes_unref (g_queue_pop_head (GET_PRIV(self)->out_queue));
|
||||
GET_PRIV(self)->out_queued = 0;
|
||||
|
||||
G_OBJECT_CLASS (cockpit_stream_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_stream_finalize (GObject *object)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (object);
|
||||
|
||||
g_assert (GET_PRIV(self)->closed);
|
||||
g_assert (!GET_PRIV(self)->in_source);
|
||||
g_assert (!GET_PRIV(self)->out_source);
|
||||
|
||||
g_byte_array_unref (GET_PRIV(self)->in_buffer);
|
||||
g_queue_free (GET_PRIV(self)->out_queue);
|
||||
g_free (GET_PRIV(self)->problem);
|
||||
g_free (GET_PRIV(self)->name);
|
||||
|
||||
if (GET_PRIV(self)->context)
|
||||
g_main_context_unref (GET_PRIV(self)->context);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_stream_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_stream_class_init (CockpitStreamClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
gobject_class->constructed = cockpit_stream_constructed;
|
||||
gobject_class->get_property = cockpit_stream_get_property;
|
||||
gobject_class->set_property = cockpit_stream_set_property;
|
||||
gobject_class->dispose = cockpit_stream_dispose;
|
||||
gobject_class->finalize = cockpit_stream_finalize;
|
||||
|
||||
/**
|
||||
* CockpitStream:io-stream:
|
||||
*
|
||||
* The underlying io stream. The input and output streams should
|
||||
* be pollable.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_IO_STREAM,
|
||||
g_param_spec_object ("io-stream", "io-stream", "io-stream", G_TYPE_IO_STREAM,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* CockpitStream:name:
|
||||
*
|
||||
* Pipe name used for debugging purposes.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_NAME,
|
||||
g_param_spec_string ("name", "name", "name", "<unnamed>",
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* CockpitStream:problem:
|
||||
*
|
||||
* The problem that the pipe closed with. If used as a constructor argument then
|
||||
* the pipe will be created in a closed/failed state. Although 'closed' signal will
|
||||
* only fire once main loop is hit.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_PROBLEM,
|
||||
g_param_spec_string ("problem", "problem", "problem", NULL,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* CockpitStream::open:
|
||||
*
|
||||
* Emitted when actually open and connected.
|
||||
*/
|
||||
cockpit_stream_sig_open = g_signal_new ("open", COCKPIT_TYPE_STREAM, G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (CockpitStreamClass, open),
|
||||
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
|
||||
/**
|
||||
* CockpitStream::read:
|
||||
* @buffer: a GByteArray of the read data
|
||||
* @eof: whether the pipe is done reading
|
||||
*
|
||||
* Emitted when data is read from the input file descriptor of the
|
||||
* pipe.
|
||||
*
|
||||
* Data consumed from @buffer by the handler should be removed from
|
||||
* the GByteArray. This can be done with the cockpit_stream_consume()
|
||||
* function.
|
||||
*
|
||||
* This handler will only be called once with @eof set to TRUE. But
|
||||
* in error conditions it may not be called with @eof set to TRUE
|
||||
* at all, and the CockpitStream::close signal will simply fire.
|
||||
*/
|
||||
cockpit_stream_sig_read = g_signal_new ("read", COCKPIT_TYPE_STREAM, G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (CockpitStreamClass, read),
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 2, G_TYPE_BYTE_ARRAY, G_TYPE_BOOLEAN);
|
||||
|
||||
/**
|
||||
* CockpitStream::close:
|
||||
* @problem: problem string or %NULL
|
||||
*
|
||||
* Emitted when the pipe closes, whether due to a problem or a normal
|
||||
* shutdown.
|
||||
*
|
||||
* @problem will be NULL if the pipe closed normally.
|
||||
*/
|
||||
cockpit_stream_sig_close = g_signal_new ("close", COCKPIT_TYPE_STREAM, G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (CockpitStreamClass, close),
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, G_TYPE_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_stream_write:
|
||||
* @self: the pipe
|
||||
* @data: the data to write
|
||||
*
|
||||
* Write @data to the pipe. This is not done immediately, it's
|
||||
* queued and written when the pipe is ready.
|
||||
*
|
||||
* If you cockpit_stream_close() with a @problem, then queued data
|
||||
* will be discarded.
|
||||
*
|
||||
* Calling this function on a closed or closing pipe (one on which
|
||||
* cockpit_stream_close() has been called) is invalid.
|
||||
*
|
||||
* Zero length data blocks are ignored, it doesn't makes sense to
|
||||
* write zero bytes to a pipe.
|
||||
*/
|
||||
void
|
||||
cockpit_stream_write (CockpitStream *self,
|
||||
GBytes *data)
|
||||
{
|
||||
gsize size, before;
|
||||
|
||||
g_return_if_fail (COCKPIT_IS_STREAM (self));
|
||||
g_return_if_fail (!GET_PRIV(self)->closing);
|
||||
|
||||
g_return_if_fail (!GET_PRIV(self)->closed);
|
||||
|
||||
size = g_bytes_get_size (data);
|
||||
if (size == 0)
|
||||
{
|
||||
g_debug ("%s: ignoring zero byte data block", GET_PRIV(self)->name);
|
||||
return;
|
||||
}
|
||||
|
||||
before = GET_PRIV(self)->out_queued;
|
||||
g_return_if_fail (G_MAXSIZE - size > GET_PRIV(self)->out_queued);
|
||||
GET_PRIV(self)->out_queued += size;
|
||||
g_queue_push_tail (GET_PRIV(self)->out_queue, g_bytes_ref (data));
|
||||
|
||||
/* No longer have pressure in the queue? */
|
||||
if (before < QUEUE_PRESSURE && GET_PRIV(self)->out_queued >= QUEUE_PRESSURE)
|
||||
cockpit_flow_emit_pressure (COCKPIT_FLOW (self), TRUE);
|
||||
|
||||
if (!GET_PRIV(self)->out_source && !GET_PRIV(self)->out_closed)
|
||||
{
|
||||
start_output (self);
|
||||
}
|
||||
|
||||
/*
|
||||
* If this becomes thread-safe, then something like this is needed:
|
||||
* g_main_context_wakeup (g_source_get_context (GET_PRIV(self)->source));
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_stream_close:
|
||||
* @self: a pipe
|
||||
* @problem: a problem or NULL
|
||||
*
|
||||
* Close the pipe. If @problem is non NULL, then it's treated
|
||||
* as if an error occurred, and the pipe is closed immediately.
|
||||
* Otherwise the pipe output is closed when all data has been sent.
|
||||
*
|
||||
* The 'close' signal will be fired when the pipe actually closes.
|
||||
* This may be during this function call (esp. in the case of a
|
||||
* non-NULL @problem) or later.
|
||||
*/
|
||||
void
|
||||
cockpit_stream_close (CockpitStream *self,
|
||||
const gchar *problem)
|
||||
{
|
||||
g_return_if_fail (COCKPIT_IS_STREAM (self));
|
||||
|
||||
GET_PRIV(self)->closing = TRUE;
|
||||
|
||||
if (problem)
|
||||
close_immediately (self, problem);
|
||||
else if (g_queue_is_empty (GET_PRIV(self)->out_queue))
|
||||
close_output (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_later_close (gpointer user_data)
|
||||
{
|
||||
close_immediately (user_data, NULL); /* problem already set */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_close_later (CockpitStream *self)
|
||||
{
|
||||
GSource *source = g_idle_source_new ();
|
||||
g_source_set_priority (source, G_PRIORITY_HIGH);
|
||||
g_source_set_callback (source, on_later_close, g_object_ref (self), g_object_unref);
|
||||
g_source_attach (source, g_main_context_get_thread_default ());
|
||||
g_source_unref (source);
|
||||
}
|
||||
|
||||
static void
|
||||
on_connect_stream (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (user_data);
|
||||
GError *error = NULL;
|
||||
GIOStream *io;
|
||||
|
||||
io = cockpit_connect_stream_finish (result, &error);
|
||||
if (error)
|
||||
{
|
||||
set_problem_from_error (self, "couldn't connect", error);
|
||||
close_immediately (self, NULL);
|
||||
g_error_free (error);
|
||||
}
|
||||
else if (!GET_PRIV(self)->closed)
|
||||
{
|
||||
GET_PRIV(self)->io = g_object_ref (io);
|
||||
initialize_io (self);
|
||||
}
|
||||
|
||||
g_clear_object (&io);
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
CockpitStream *
|
||||
cockpit_stream_connect (const gchar *name,
|
||||
CockpitConnectable *connectable)
|
||||
{
|
||||
CockpitStream *self;
|
||||
|
||||
g_return_val_if_fail (connectable != NULL, NULL);
|
||||
|
||||
self = g_object_new (COCKPIT_TYPE_STREAM,
|
||||
"io-stream", NULL,
|
||||
"name", name,
|
||||
NULL);
|
||||
|
||||
GET_PRIV(self)->connecting = cockpit_connectable_ref (connectable);
|
||||
cockpit_connect_stream_full (GET_PRIV(self)->connecting, NULL,
|
||||
on_connect_stream, g_object_ref (self));
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_stream_get_name:
|
||||
* @self: a pipe
|
||||
*
|
||||
* Get the name of the pipe.
|
||||
*
|
||||
* This is used for logging.
|
||||
*
|
||||
* Returns: (transfer none): the name
|
||||
*/
|
||||
const gchar *
|
||||
cockpit_stream_get_name (CockpitStream *self)
|
||||
{
|
||||
g_return_val_if_fail (COCKPIT_IS_STREAM (self), NULL);
|
||||
return GET_PRIV(self)->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_stream_get_buffer:
|
||||
* @self: a pipe
|
||||
*
|
||||
* Get the input buffer for the pipe.
|
||||
*
|
||||
* This can change when the main loop is run. You can use
|
||||
* cockpit_pipe_consume() to consume data from it.
|
||||
*
|
||||
* Returns: (transfer none): the buffer
|
||||
*/
|
||||
GByteArray *
|
||||
cockpit_stream_get_buffer (CockpitStream *self)
|
||||
{
|
||||
g_return_val_if_fail (COCKPIT_IS_STREAM (self), NULL);
|
||||
return GET_PRIV(self)->in_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* cockpit_stream_new:
|
||||
* @name: a name for debugging
|
||||
* @io_stream: A stream to wrap
|
||||
*
|
||||
* Create a stream for the given io stream
|
||||
*
|
||||
* Returns: (transfer full): a new CockpitStream
|
||||
*/
|
||||
CockpitStream *
|
||||
cockpit_stream_new (const gchar *name,
|
||||
GIOStream *io_stream)
|
||||
{
|
||||
return g_object_new (COCKPIT_TYPE_STREAM,
|
||||
"name", name,
|
||||
"io-stream", io_stream,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_throttle_pressure (GObject *object,
|
||||
gboolean throttle,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (user_data);
|
||||
if (throttle)
|
||||
{
|
||||
if (GET_PRIV(self)->in_source != NULL)
|
||||
{
|
||||
g_debug ("%s: applying back pressure in stream", GET_PRIV(self)->name);
|
||||
stop_input (self);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GET_PRIV(self)->in_source == NULL && GET_PRIV(self)->io && !GET_PRIV(self)->connecting)
|
||||
{
|
||||
g_debug ("%s: relieving back pressure in stream", GET_PRIV(self)->name);
|
||||
start_input (self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_stream_throttle (CockpitFlow *flow,
|
||||
CockpitFlow *controlling)
|
||||
{
|
||||
CockpitStream *self = COCKPIT_STREAM (flow);
|
||||
|
||||
if (GET_PRIV(self)->pressure)
|
||||
{
|
||||
g_signal_handler_disconnect (GET_PRIV(self)->pressure, GET_PRIV(self)->pressure_sig);
|
||||
g_object_remove_weak_pointer (G_OBJECT (GET_PRIV(self)->pressure), (gpointer *)&GET_PRIV(self)->pressure);
|
||||
GET_PRIV(self)->pressure = NULL;
|
||||
}
|
||||
|
||||
if (controlling)
|
||||
{
|
||||
GET_PRIV(self)->pressure = controlling;
|
||||
g_object_add_weak_pointer (G_OBJECT (GET_PRIV(self)->pressure), (gpointer *)&GET_PRIV(self)->pressure);
|
||||
GET_PRIV(self)->pressure_sig = g_signal_connect (controlling, "pressure", G_CALLBACK (on_throttle_pressure), self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_stream_flow_iface_init (CockpitFlowInterface *iface)
|
||||
{
|
||||
iface->throttle = cockpit_stream_throttle;
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "cockpitconnect.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
#define COCKPIT_TYPE_STREAM (cockpit_stream_get_type ())
|
||||
G_DECLARE_DERIVABLE_TYPE(CockpitStream, cockpit_stream, COCKPIT, STREAM, GObject)
|
||||
|
||||
struct _CockpitStreamClass {
|
||||
GObjectClass parent_class;
|
||||
|
||||
/* signals */
|
||||
|
||||
void (* open) (CockpitStream *stream);
|
||||
|
||||
void (* read) (CockpitStream *pipe,
|
||||
GByteArray *buffer,
|
||||
gboolean eof);
|
||||
|
||||
void (* close) (CockpitStream *pipe,
|
||||
const gchar *problem);
|
||||
};
|
||||
|
||||
CockpitStream * cockpit_stream_new (const gchar *name,
|
||||
GIOStream *stream);
|
||||
|
||||
CockpitStream * cockpit_stream_connect (const gchar *name,
|
||||
CockpitConnectable *connectable);
|
||||
|
||||
void cockpit_stream_write (CockpitStream *self,
|
||||
GBytes *data);
|
||||
|
||||
void cockpit_stream_close (CockpitStream *self,
|
||||
const gchar *problem);
|
||||
|
||||
const gchar * cockpit_stream_get_name (CockpitStream *self);
|
||||
|
||||
GByteArray * cockpit_stream_get_buffer (CockpitStream *self);
|
||||
|
||||
const gchar * cockpit_stream_problem (GError *error,
|
||||
const gchar *name,
|
||||
const gchar *summary,
|
||||
JsonObject *object);
|
||||
|
|
@ -1,408 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitwebsocketstream.h"
|
||||
|
||||
#include "cockpitconnect.h"
|
||||
#include "cockpitstream.h"
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
#include "common/cockpitflow.h"
|
||||
#include "common/cockpitjson.h"
|
||||
|
||||
#include "websocket/websocket.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* CockpitWebSocketStream:
|
||||
*
|
||||
* A #CockpitChannel that represents a WebSocket client
|
||||
*
|
||||
* The payload type for this channel is 'websocket-stream1'.
|
||||
*/
|
||||
|
||||
#define COCKPIT_WEB_SOCKET_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_WEB_SOCKET_STREAM, CockpitWebSocketStream))
|
||||
|
||||
typedef struct _CockpitWebSocketStream {
|
||||
CockpitChannel parent;
|
||||
|
||||
/* The nickname for debugging and logging */
|
||||
gchar *url;
|
||||
gchar *origin;
|
||||
|
||||
/* The connection */
|
||||
WebSocketConnection *client;
|
||||
gulong sig_open;
|
||||
gulong sig_message;
|
||||
gulong sig_closing;
|
||||
gulong sig_close;
|
||||
gulong sig_error;
|
||||
|
||||
gboolean binary;
|
||||
gboolean closed;
|
||||
gushort last_error_code;
|
||||
|
||||
} CockpitWebSocketStream;
|
||||
|
||||
G_DEFINE_TYPE (CockpitWebSocketStream, cockpit_web_socket_stream, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
cockpit_web_socket_stream_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel);
|
||||
WebSocketDataType type;
|
||||
WebSocketState state;
|
||||
|
||||
/* Should never be called before cockpit_channel_ready() */
|
||||
g_return_if_fail (self->client != NULL);
|
||||
|
||||
state = web_socket_connection_get_ready_state (self->client);
|
||||
g_return_if_fail (state >= WEB_SOCKET_STATE_OPEN);
|
||||
|
||||
if (state == WEB_SOCKET_STATE_OPEN)
|
||||
{
|
||||
type = self->binary ? WEB_SOCKET_DATA_BINARY : WEB_SOCKET_DATA_TEXT;
|
||||
web_socket_connection_send (self->client, type, NULL, message);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
cockpit_web_socket_stream_control (CockpitChannel *channel,
|
||||
const gchar *command,
|
||||
JsonObject *options)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel);
|
||||
|
||||
if (!g_str_equal (command, "done"))
|
||||
return FALSE;
|
||||
|
||||
if (self->client && web_socket_connection_get_ready_state (self->client) == WEB_SOCKET_STATE_OPEN)
|
||||
web_socket_connection_close (self->client, WEB_SOCKET_CLOSE_NORMAL, "disconnected");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_web_socket_stream_close (CockpitChannel *channel,
|
||||
const gchar *problem)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel);
|
||||
|
||||
self->closed = TRUE;
|
||||
if (self->client && web_socket_connection_get_ready_state (self->client) < WEB_SOCKET_STATE_CLOSING)
|
||||
{
|
||||
if (problem)
|
||||
web_socket_connection_close (self->client, WEB_SOCKET_CLOSE_ABNORMAL, problem);
|
||||
else
|
||||
web_socket_connection_close (self->client, WEB_SOCKET_CLOSE_NORMAL, "disconnected");
|
||||
}
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_web_socket_stream_parent_class)->close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_web_socket_stream_init (CockpitWebSocketStream *self)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
on_web_socket_open (WebSocketConnection *connection,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (user_data);
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (user_data);
|
||||
JsonObject *object;
|
||||
JsonObject *headers;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
|
||||
headers = json_object_new ();
|
||||
|
||||
g_hash_table_iter_init (&iter, web_socket_client_get_headers (WEB_SOCKET_CLIENT (self->client)));
|
||||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||||
json_object_set_string_member (headers, key, value);
|
||||
|
||||
object = json_object_new ();
|
||||
json_object_set_object_member (object, "headers", headers);
|
||||
|
||||
cockpit_channel_control (channel, "response", object);
|
||||
json_object_unref (object);
|
||||
|
||||
cockpit_channel_ready (channel, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_web_socket_message (WebSocketConnection *connection,
|
||||
WebSocketDataType type,
|
||||
GBytes *message,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (user_data);
|
||||
cockpit_channel_send (channel, message, type == WEB_SOCKET_DATA_TEXT);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_web_socket_closing (WebSocketConnection *connection,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (user_data);
|
||||
cockpit_channel_control (channel, "done", NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_web_socket_error (WebSocketConnection *ws,
|
||||
GError *error,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (user_data);
|
||||
self->last_error_code = 0;
|
||||
if (error && error->domain == WEB_SOCKET_ERROR)
|
||||
self->last_error_code = error->code;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_web_socket_close (WebSocketConnection *connection,
|
||||
gpointer user_data)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (user_data);
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (user_data);
|
||||
const gchar *problem;
|
||||
gushort code;
|
||||
|
||||
code = web_socket_connection_get_close_code (connection);
|
||||
problem = web_socket_connection_get_close_data (connection);
|
||||
|
||||
if (code == WEB_SOCKET_CLOSE_NORMAL || code == WEB_SOCKET_CLOSE_GOING_AWAY)
|
||||
{
|
||||
problem = NULL;
|
||||
}
|
||||
else if (problem == NULL || !problem[0])
|
||||
{
|
||||
/* If we don't have a code but have a last error
|
||||
* use it's code */
|
||||
if (code == 0)
|
||||
code = self->last_error_code;
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case WEB_SOCKET_CLOSE_NO_STATUS:
|
||||
case WEB_SOCKET_CLOSE_ABNORMAL:
|
||||
problem = "disconnected";
|
||||
break;
|
||||
case WEB_SOCKET_CLOSE_PROTOCOL:
|
||||
case WEB_SOCKET_CLOSE_UNSUPPORTED_DATA:
|
||||
case WEB_SOCKET_CLOSE_BAD_DATA:
|
||||
case WEB_SOCKET_CLOSE_POLICY_VIOLATION:
|
||||
case WEB_SOCKET_CLOSE_TOO_BIG:
|
||||
case WEB_SOCKET_CLOSE_TLS_HANDSHAKE:
|
||||
problem = "protocol-error";
|
||||
break;
|
||||
case WEB_SOCKET_CLOSE_NO_EXTENSION:
|
||||
problem = "unsupported";
|
||||
break;
|
||||
default:
|
||||
problem = "internal-error";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cockpit_channel_close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
on_socket_connect (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(CockpitWebSocketStream) self = user_data; /* capture the ref passed to the async op */
|
||||
CockpitChannel *channel = COCKPIT_CHANNEL (self);
|
||||
g_autofree const gchar **protocols = NULL;
|
||||
g_autoptr(GList) names = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
g_autoptr(GIOStream) io = cockpit_connect_stream_finish (result, &error);
|
||||
if (error)
|
||||
{
|
||||
const char *problem = cockpit_stream_problem (error, self->origin, "couldn't connect",
|
||||
cockpit_channel_close_options (channel));
|
||||
cockpit_channel_close (channel, problem);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject *options = cockpit_channel_get_options (channel);
|
||||
|
||||
if (!cockpit_json_get_strv (options, "protocols", NULL, &protocols))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"%s: invalid \"protocol\" value in WebSocket stream request", self->origin);
|
||||
return;
|
||||
}
|
||||
|
||||
self->client = web_socket_client_new_for_stream (self->url, self->origin, protocols, io);
|
||||
|
||||
JsonNode *node = json_object_get_member (options, "headers");
|
||||
if (node)
|
||||
{
|
||||
if (!JSON_NODE_HOLDS_OBJECT (node))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"%s: invalid \"headers\" field in WebSocket stream request", self->origin);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject *headers = json_node_get_object (node);
|
||||
names = json_object_get_members (headers);
|
||||
for (GList *l = names; l != NULL; l = g_list_next (l))
|
||||
{
|
||||
node = json_object_get_member (headers, l->data);
|
||||
if (!node || !JSON_NODE_HOLDS_VALUE (node) || json_node_get_value_type (node) != G_TYPE_STRING)
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"%s: invalid header value in WebSocket stream request: %s",
|
||||
self->origin, (gchar *)l->data);
|
||||
return;
|
||||
}
|
||||
const gchar *value = json_node_get_string (node);
|
||||
|
||||
g_debug ("%s: sending header: %s %s", self->origin, (gchar *)l->data, value);
|
||||
web_socket_client_include_header (WEB_SOCKET_CLIENT (self->client), l->data, value);
|
||||
}
|
||||
}
|
||||
|
||||
self->sig_open = g_signal_connect (self->client, "open", G_CALLBACK (on_web_socket_open), self);
|
||||
self->sig_message = g_signal_connect (self->client, "message", G_CALLBACK (on_web_socket_message), self);
|
||||
self->sig_closing = g_signal_connect (self->client, "closing", G_CALLBACK (on_web_socket_closing), self);
|
||||
self->sig_close = g_signal_connect (self->client, "close", G_CALLBACK (on_web_socket_close), self);
|
||||
self->sig_error = g_signal_connect (self->client, "error", G_CALLBACK (on_web_socket_error), self);
|
||||
|
||||
/* Let the channel throttle the websocket's input flow*/
|
||||
cockpit_flow_throttle (COCKPIT_FLOW (self->client), COCKPIT_FLOW (self));
|
||||
|
||||
/* Let the websocket throtlte the channel peer's output flow */
|
||||
cockpit_flow_throttle (COCKPIT_FLOW (channel), COCKPIT_FLOW (self->client));
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_web_socket_stream_prepare (CockpitChannel *channel)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel);
|
||||
CockpitConnectable *connectable = NULL;
|
||||
JsonObject *options;
|
||||
const gchar *path;
|
||||
|
||||
COCKPIT_CHANNEL_CLASS (cockpit_web_socket_stream_parent_class)->prepare (channel);
|
||||
|
||||
if (self->closed)
|
||||
goto out;
|
||||
|
||||
connectable = cockpit_connect_parse_stream (channel);
|
||||
if (!connectable)
|
||||
goto out;
|
||||
|
||||
options = cockpit_channel_get_options (channel);
|
||||
if (!cockpit_json_get_string (options, "path", NULL, &path))
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"%s: bad \"path\" field in WebSocket stream request", self->origin);
|
||||
goto out;
|
||||
}
|
||||
else if (path == NULL || path[0] != '/')
|
||||
{
|
||||
cockpit_channel_fail (channel, "protocol-error",
|
||||
"%s: invalid or missing \"path\" field in WebSocket stream request", self->origin);
|
||||
goto out;
|
||||
}
|
||||
|
||||
self->url = g_strdup_printf ("%s://%s%s", connectable->tls ? "wss" : "ws", connectable->name, path);
|
||||
self->origin = g_strdup_printf ("%s://%s", connectable->tls ? "https" : "http", connectable->name);
|
||||
|
||||
/* Parsed elsewhere */
|
||||
self->binary = json_object_has_member (options, "binary");
|
||||
|
||||
cockpit_connect_stream_full (connectable, NULL, on_socket_connect, g_object_ref (self));
|
||||
|
||||
out:
|
||||
if (connectable)
|
||||
cockpit_connectable_unref (connectable);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_web_socket_stream_dispose (GObject *object)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (object);
|
||||
|
||||
if (self->client)
|
||||
{
|
||||
if (web_socket_connection_get_ready_state (self->client) < WEB_SOCKET_STATE_CLOSING)
|
||||
web_socket_connection_close (self->client, WEB_SOCKET_CLOSE_GOING_AWAY, "disconnected");
|
||||
g_signal_handler_disconnect (self->client, self->sig_open);
|
||||
g_signal_handler_disconnect (self->client, self->sig_message);
|
||||
g_signal_handler_disconnect (self->client, self->sig_closing);
|
||||
g_signal_handler_disconnect (self->client, self->sig_close);
|
||||
g_signal_handler_disconnect (self->client, self->sig_error);
|
||||
|
||||
g_object_unref (self->client);
|
||||
self->client = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (cockpit_web_socket_stream_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_web_socket_stream_finalize (GObject *object)
|
||||
{
|
||||
CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (object);
|
||||
|
||||
g_free (self->url);
|
||||
g_free (self->origin);
|
||||
g_assert (self->client == NULL);
|
||||
|
||||
G_OBJECT_CLASS (cockpit_web_socket_stream_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_web_socket_stream_constructed (GObject *object)
|
||||
{
|
||||
static const gchar *caps[] = { "tls-certificates", "address", NULL };
|
||||
G_OBJECT_CLASS (cockpit_web_socket_stream_parent_class)->constructed (object);
|
||||
g_object_set (object, "capabilities", &caps, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
cockpit_web_socket_stream_class_init (CockpitWebSocketStreamClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = cockpit_web_socket_stream_dispose;
|
||||
gobject_class->finalize = cockpit_web_socket_stream_finalize;
|
||||
gobject_class->constructed = cockpit_web_socket_stream_constructed;
|
||||
|
||||
channel_class->prepare = cockpit_web_socket_stream_prepare;
|
||||
channel_class->control = cockpit_web_socket_stream_control;
|
||||
channel_class->recv = cockpit_web_socket_stream_recv;
|
||||
channel_class->close = cockpit_web_socket_stream_close;
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
|
||||
#define COCKPIT_TYPE_WEB_SOCKET_STREAM (cockpit_web_socket_stream_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (CockpitWebSocketStream, cockpit_web_socket_stream,
|
||||
COCKPIT, WEB_SOCKET_STREAM, CockpitChannel)
|
||||
|
|
@ -1,352 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
#include "common/cockpithacks-glib.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitpipetransport.h"
|
||||
#include "common/cockpitsystem.h"
|
||||
|
||||
#include <glib-unix.h>
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
/*
|
||||
* The bridge implements two channel types.
|
||||
*
|
||||
* 'uppercase': Make all data upper case
|
||||
* 'lowercase': Make all data lower case
|
||||
*
|
||||
* By default only the first one is available. If run with --lower
|
||||
* then the latter is available.
|
||||
*/
|
||||
|
||||
static gboolean opt_lower;
|
||||
static gboolean opt_upper;
|
||||
static gboolean opt_show_count;
|
||||
static gboolean opt_problem;
|
||||
|
||||
static GHashTable *channels;
|
||||
|
||||
static GType mock_case_channel_get_type (void) G_GNUC_CONST;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
gchar (* function) (gchar);
|
||||
} MockCaseChannel;
|
||||
|
||||
typedef CockpitChannelClass MockCaseChannelClass;
|
||||
|
||||
G_DEFINE_TYPE (MockCaseChannel, mock_case_channel, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
mock_case_channel_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
MockCaseChannel *self = (MockCaseChannel *)channel;
|
||||
GByteArray *array = g_bytes_unref_to_array (g_bytes_ref (message));
|
||||
GBytes *bytes;
|
||||
gsize i;
|
||||
|
||||
for (i = 0; i < array->len; i++)
|
||||
array->data[i] = self->function(array->data[i]);
|
||||
|
||||
bytes = g_byte_array_free_to_bytes (array);
|
||||
cockpit_channel_send (channel, bytes, FALSE);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
mock_case_channel_control (CockpitChannel *channel,
|
||||
const gchar *command,
|
||||
JsonObject *options)
|
||||
{
|
||||
if (g_strcmp0 (command, "close-later") == 0)
|
||||
cockpit_channel_close (channel, "closed");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
mock_case_channel_init (MockCaseChannel *self)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
mock_case_channel_constructed (GObject *obj)
|
||||
{
|
||||
MockCaseChannel *self = (MockCaseChannel *)obj;
|
||||
const gchar *payload = NULL;
|
||||
JsonObject *options;
|
||||
JsonObject *ready = json_object_new ();
|
||||
|
||||
const gchar *env = g_getenv ("COCKPIT_TEST_PARAM_ENV");
|
||||
|
||||
G_OBJECT_CLASS (mock_case_channel_parent_class)->constructed (obj);
|
||||
|
||||
options = cockpit_channel_get_options (COCKPIT_CHANNEL (obj));
|
||||
if (!cockpit_json_get_string (options, "payload", NULL, &payload))
|
||||
g_assert_not_reached ();
|
||||
|
||||
if (g_strcmp0 (payload, "upper") == 0)
|
||||
self->function = g_ascii_toupper;
|
||||
else if (g_strcmp0 (payload, "lower") == 0)
|
||||
self->function = g_ascii_tolower;
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
|
||||
if (env)
|
||||
json_object_set_string_member (ready, "test-env", env);
|
||||
|
||||
if (opt_show_count)
|
||||
json_object_set_int_member (ready, "count", g_hash_table_size (channels));
|
||||
|
||||
cockpit_channel_ready (COCKPIT_CHANNEL (self), ready);
|
||||
json_object_unref (ready);
|
||||
}
|
||||
|
||||
static void
|
||||
mock_case_channel_class_init (MockCaseChannelClass *klass)
|
||||
{
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->constructed = mock_case_channel_constructed;
|
||||
channel_class->recv = mock_case_channel_recv;
|
||||
channel_class->control = mock_case_channel_control;
|
||||
}
|
||||
|
||||
static gboolean init_received;
|
||||
static gboolean opt_lower;
|
||||
|
||||
static void
|
||||
on_channel_closed (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_hash_table_remove (channels, cockpit_channel_get_id (channel));
|
||||
}
|
||||
|
||||
static void
|
||||
process_init (CockpitTransport *transport,
|
||||
JsonObject *options)
|
||||
{
|
||||
gint64 version = -1;
|
||||
|
||||
if (!cockpit_json_get_int (options, "version", -1, &version))
|
||||
{
|
||||
g_warning ("invalid version field in init message");
|
||||
cockpit_transport_close (transport, "protocol-error");
|
||||
}
|
||||
|
||||
if (version == 1)
|
||||
{
|
||||
g_debug ("received init message");
|
||||
init_received = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_message ("unsupported version of cockpit protocol: %" G_GINT64_FORMAT, version);
|
||||
cockpit_transport_close (transport, "not-supported");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
process_open (CockpitTransport *transport,
|
||||
const gchar *channel_id,
|
||||
JsonObject *options)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
GType channel_type;
|
||||
const gchar *payload;
|
||||
|
||||
if (!channel_id)
|
||||
{
|
||||
g_warning ("Caller tried to open channel with invalid id");
|
||||
cockpit_transport_close (transport, "protocol-error");
|
||||
}
|
||||
else if (g_hash_table_lookup (channels, channel_id))
|
||||
{
|
||||
g_warning ("Caller tried to reuse a channel that's already in use");
|
||||
cockpit_transport_close (transport, "protocol-error");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!cockpit_json_get_string (options, "payload", NULL, &payload))
|
||||
payload = NULL;
|
||||
|
||||
/* This will close with "not-supported" */
|
||||
channel_type = COCKPIT_TYPE_CHANNEL;
|
||||
|
||||
if ((opt_lower && g_strcmp0 (payload, "lower") == 0) ||
|
||||
(opt_upper && g_strcmp0 (payload, "upper") == 0))
|
||||
channel_type = mock_case_channel_get_type ();
|
||||
|
||||
channel = g_object_new (channel_type,
|
||||
"transport", transport,
|
||||
"id", channel_id,
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
g_hash_table_insert (channels, g_strdup (channel_id), channel);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_channel_closed), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_transport_control (CockpitTransport *transport,
|
||||
const char *command,
|
||||
const gchar *channel_id,
|
||||
JsonObject *options,
|
||||
GBytes *message,
|
||||
gpointer user_data)
|
||||
{
|
||||
if (g_str_equal (command, "init"))
|
||||
{
|
||||
process_init (transport, options);
|
||||
return TRUE;
|
||||
}
|
||||
else if (!init_received)
|
||||
{
|
||||
g_warning ("caller did not send 'init' message first");
|
||||
cockpit_transport_close (transport, "protocol-error");
|
||||
return TRUE;
|
||||
}
|
||||
else if (g_str_equal (command, "open"))
|
||||
{
|
||||
process_open (transport, channel_id, options);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_closed_set_flag (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gboolean *flag = user_data;
|
||||
*flag = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
send_init_command (CockpitTransport *transport)
|
||||
{
|
||||
JsonObject *object;
|
||||
GBytes *bytes;
|
||||
|
||||
object = json_object_new ();
|
||||
json_object_set_string_member (object, "command", "init");
|
||||
json_object_set_int_member (object, "version", 1);
|
||||
|
||||
if (opt_problem)
|
||||
{
|
||||
json_object_set_string_member (object, "problem", "canna-do-it");
|
||||
json_object_set_string_member (object, "another-field", "extra");
|
||||
}
|
||||
|
||||
bytes = cockpit_json_write_bytes (object);
|
||||
json_object_unref (object);
|
||||
|
||||
cockpit_transport_send (transport, NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_signal_done (gpointer data)
|
||||
{
|
||||
gboolean *closed = data;
|
||||
*closed = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char **argv)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
gboolean terminated = FALSE;
|
||||
gboolean interrupted = FALSE;
|
||||
gboolean closed = FALSE;
|
||||
GOptionContext *context;
|
||||
GError *error = NULL;
|
||||
guint sig_term;
|
||||
guint sig_int;
|
||||
|
||||
static GOptionEntry entries[] = {
|
||||
{ "lower", 0, 0, G_OPTION_ARG_NONE, &opt_lower, "Lower case channel type", NULL },
|
||||
{ "count", 0, 0, G_OPTION_ARG_NONE, &opt_show_count, "Show open channels count in ready messages", NULL },
|
||||
{ "upper", 0, 0, G_OPTION_ARG_NONE, &opt_upper, "Upper case channel type", NULL },
|
||||
{ "problem", 0, 0, G_OPTION_ARG_NONE, &opt_problem, "Set a problem in \"init\" message", NULL },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
signal (SIGPIPE, SIG_IGN);
|
||||
|
||||
cockpit_setenv_check ("GSETTINGS_BACKEND", "memory", TRUE);
|
||||
cockpit_setenv_check ("GIO_USE_PROXY_RESOLVER", "dummy", TRUE);
|
||||
cockpit_setenv_check ("GIO_USE_VFS", "local", TRUE);
|
||||
|
||||
context = g_option_context_new (NULL);
|
||||
g_option_context_add_main_entries (context, entries, NULL);
|
||||
g_option_context_set_description (context, "mock-bridge as used in tests\n");
|
||||
|
||||
g_option_context_parse (context, &argc, &argv, &error);
|
||||
g_option_context_free (context);
|
||||
|
||||
if (error)
|
||||
{
|
||||
g_printerr ("mock-bridge: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return 255;
|
||||
}
|
||||
|
||||
cockpit_hacks_redirect_gdebug_to_stderr ();
|
||||
|
||||
sig_term = g_unix_signal_add (SIGTERM, on_signal_done, &terminated);
|
||||
sig_int = g_unix_signal_add (SIGINT, on_signal_done, &interrupted);
|
||||
|
||||
transport = cockpit_pipe_transport_new_fds ("stdio", 0, 1);
|
||||
|
||||
g_signal_connect (transport, "control", G_CALLBACK (on_transport_control), NULL);
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
send_init_command (transport);
|
||||
|
||||
/* Owns the channels */
|
||||
channels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
||||
|
||||
while (!terminated && !closed && !interrupted)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_object_unref (transport);
|
||||
g_hash_table_destroy (channels);
|
||||
|
||||
g_source_remove (sig_term);
|
||||
g_source_remove (sig_int);
|
||||
|
||||
/* So the caller gets the right signal */
|
||||
if (terminated)
|
||||
raise (SIGTERM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
/*
|
||||
* Trivial, configurable PMDA
|
||||
*
|
||||
* Copyright (c) 2014 Red Hat.
|
||||
* Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <pcp/pmapi.h>
|
||||
#include <pcp/pmda.h>
|
||||
|
||||
#if PM_VERSION_CURRENT < PM_VERSION(4,0,0)
|
||||
#include <pcp/impl.h>
|
||||
#define pmID_cluster(pmid) pmid_cluster(pmid)
|
||||
#define pmID_item(pmid) pmid_item(pmid)
|
||||
#endif
|
||||
|
||||
static pmdaInstid inst_values[] = {
|
||||
{ 1, "red" }, { 2, "green" }, { 3, "blue" }
|
||||
};
|
||||
|
||||
static pmdaIndom indomtab[] = {
|
||||
#define VALUES_INDOM 0
|
||||
{ VALUES_INDOM, sizeof(inst_values)/sizeof(inst_values[0]), inst_values },
|
||||
#define INSTANCES_INDOM 1
|
||||
{ INSTANCES_INDOM, 0, NULL }
|
||||
};
|
||||
|
||||
static pmdaMetric metrictab[] = {
|
||||
/* value */
|
||||
{ NULL,
|
||||
{ PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
|
||||
PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
|
||||
/* values */
|
||||
{ NULL,
|
||||
{ PMDA_PMID(0,1), PM_TYPE_U32, VALUES_INDOM, PM_SEM_INSTANT,
|
||||
PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
|
||||
/* instances */
|
||||
{ NULL,
|
||||
{ PMDA_PMID(0,2), PM_TYPE_U32, INSTANCES_INDOM, PM_SEM_INSTANT,
|
||||
PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
|
||||
/* seconds */
|
||||
{ NULL,
|
||||
{ PMDA_PMID(0,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
|
||||
PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) } },
|
||||
/* string */
|
||||
{ NULL,
|
||||
{ PMDA_PMID(0,4), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
|
||||
PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
|
||||
/* counter */
|
||||
{ NULL,
|
||||
{ PMDA_PMID(0,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
|
||||
PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
|
||||
/* counter64 */
|
||||
{ NULL,
|
||||
{ PMDA_PMID(0,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
|
||||
PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }
|
||||
|
||||
};
|
||||
|
||||
static unsigned int values[4] = { 0, 0, 0 };
|
||||
|
||||
static pmInDom instances_indom;
|
||||
|
||||
static const char *string_value = "foobar";
|
||||
|
||||
static int counter = 0;
|
||||
static int64_t counter64 = INT64_MAX - 100;
|
||||
|
||||
static int
|
||||
mock_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
|
||||
{
|
||||
if (pmID_cluster(mdesc->m_desc.pmid) != 0)
|
||||
return PM_ERR_PMID;
|
||||
|
||||
switch (pmID_item(mdesc->m_desc.pmid)) {
|
||||
case 0:
|
||||
if (inst != PM_IN_NULL)
|
||||
return PM_ERR_INST;
|
||||
atom->ul = values[0];
|
||||
break;
|
||||
case 1:
|
||||
if (inst < 1 || inst > 3)
|
||||
return PM_ERR_INST;
|
||||
atom->ul = values[inst];
|
||||
break;
|
||||
case 2: {
|
||||
void *val;
|
||||
if (pmdaCacheLookup(instances_indom, inst, NULL, &val) != PMDA_CACHE_ACTIVE)
|
||||
return PM_ERR_INST;
|
||||
atom->ul = (intptr_t)val;
|
||||
} break;
|
||||
case 3:
|
||||
if (inst != PM_IN_NULL)
|
||||
return PM_ERR_INST;
|
||||
atom->ul = 60;
|
||||
break;
|
||||
case 4:
|
||||
if (inst != PM_IN_NULL)
|
||||
return PM_ERR_INST;
|
||||
atom->cp = (char *)string_value;
|
||||
break;
|
||||
case 5:
|
||||
if (inst != PM_IN_NULL)
|
||||
return PM_ERR_INST;
|
||||
atom->ul = counter;
|
||||
break;
|
||||
case 6:
|
||||
if (inst != PM_IN_NULL)
|
||||
return PM_ERR_INST;
|
||||
atom->ull = counter64;
|
||||
break;
|
||||
default:
|
||||
return PM_ERR_PMID;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mock_control (const char *cmd, ...);
|
||||
|
||||
void
|
||||
mock_control (const char *cmd, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start (ap, cmd);
|
||||
|
||||
if (strcmp (cmd, "reset") == 0)
|
||||
{
|
||||
values[0] = values[1] = values[2] = values[3] = 0;
|
||||
pmdaCacheOp (instances_indom, PMDA_CACHE_CULL);
|
||||
string_value = "foobar";
|
||||
counter = 0;
|
||||
counter64 = INT64_MAX - 100;
|
||||
}
|
||||
else if (strcmp (cmd, "set-value") == 0)
|
||||
{
|
||||
int i = va_arg (ap, int);
|
||||
int v = va_arg (ap, int);
|
||||
values[i] = v;
|
||||
}
|
||||
else if (strcmp (cmd, "add-instance") == 0)
|
||||
{
|
||||
const char *n = va_arg (ap, const char *);
|
||||
intptr_t val = va_arg (ap, int);
|
||||
pmdaCacheStore (instances_indom, PMDA_CACHE_ADD, n, (void *)val);
|
||||
}
|
||||
else if (strcmp (cmd, "del-instance") == 0)
|
||||
{
|
||||
const char *n = va_arg (ap, const char *);
|
||||
pmdaCacheStore (instances_indom, PMDA_CACHE_HIDE, n, NULL);
|
||||
}
|
||||
else if (strcmp (cmd, "set-string") == 0)
|
||||
{
|
||||
const char *n = va_arg (ap, const char *);
|
||||
string_value = n;
|
||||
}
|
||||
else if (strcmp (cmd, "inc-counter") == 0)
|
||||
{
|
||||
int val = va_arg (ap, int);
|
||||
counter += val;
|
||||
}
|
||||
else if (strcmp (cmd, "inc-counter64") == 0)
|
||||
{
|
||||
int val = va_arg (ap, int);
|
||||
counter64 += val;
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void mock_init (pmdaInterface *dp);
|
||||
|
||||
void
|
||||
mock_init (pmdaInterface *dp)
|
||||
{
|
||||
pmdaDSO(dp, PMDA_INTERFACE_2, "mock-pmda", NULL);
|
||||
|
||||
if (dp->status != 0)
|
||||
return;
|
||||
|
||||
pmdaSetFetchCallBack(dp, mock_fetchCallBack);
|
||||
pmdaInit(dp,
|
||||
indomtab, sizeof(indomtab)/sizeof(indomtab[0]),
|
||||
metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
|
||||
|
||||
instances_indom = indomtab[INSTANCES_INDOM].it_indom;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
root {
|
||||
mock
|
||||
}
|
||||
|
||||
mock {
|
||||
value 333:0:0
|
||||
values 333:0:1
|
||||
instances 333:0:2
|
||||
seconds 333:0:3
|
||||
string 333:0:4
|
||||
counter 333:0:5
|
||||
counter64 333:0:6
|
||||
}
|
||||
|
|
@ -1,309 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitpipe.h"
|
||||
#include "common/cockpitpipetransport.h"
|
||||
#include "common/cockpitsystem.h"
|
||||
#include "testlib/cockpittest.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static gboolean
|
||||
on_recv_get_bytes (CockpitTransport *transport,
|
||||
const gchar *channel,
|
||||
GBytes *data,
|
||||
gpointer user_data)
|
||||
{
|
||||
GBytes **result = (GBytes **)user_data;
|
||||
g_assert_cmpstr (channel, ==, NULL);
|
||||
g_assert (result != NULL);
|
||||
g_assert (*result == NULL);
|
||||
*result = g_bytes_ref (data);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_closed_not_reached (CockpitTransport *transport,
|
||||
const gchar *problem)
|
||||
{
|
||||
g_assert_cmpstr (problem, ==, NULL);
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_bridge_init (void)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
CockpitPipe *pipe;
|
||||
GBytes *bytes = NULL;
|
||||
JsonObject *object;
|
||||
JsonObject *os_release;
|
||||
JsonObject *packages;
|
||||
GError *error = NULL;
|
||||
GList *list;
|
||||
|
||||
const gchar *argv[] = {
|
||||
BUILDDIR "/cockpit-bridge",
|
||||
NULL
|
||||
};
|
||||
|
||||
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
|
||||
transport = cockpit_pipe_transport_new (pipe);
|
||||
g_object_unref (pipe);
|
||||
|
||||
g_signal_connect (transport, "recv", G_CALLBACK (on_recv_get_bytes), &bytes);
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_closed_not_reached), NULL);
|
||||
|
||||
while (bytes == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_signal_handlers_disconnect_by_func (transport, on_recv_get_bytes, &bytes);
|
||||
g_signal_handlers_disconnect_by_func (transport, on_closed_not_reached, NULL);
|
||||
|
||||
g_object_unref (transport);
|
||||
|
||||
object = cockpit_json_parse_bytes (bytes, &error);
|
||||
g_assert_no_error (error);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
g_assert_cmpstr (json_object_get_string_member (object, "command"), ==, "init");
|
||||
|
||||
/* Make sure we've included /etc/os-release information */
|
||||
os_release = json_object_get_object_member (object, "os-release");
|
||||
g_assert (os_release != NULL);
|
||||
g_assert (json_object_has_member (os_release, "NAME"));
|
||||
|
||||
/* Make sure we have right packages listed */
|
||||
packages = json_object_get_object_member (object, "packages");
|
||||
g_assert (packages != NULL);
|
||||
list = json_object_get_members (packages);
|
||||
list = g_list_sort (list, (GCompareFunc)strcmp);
|
||||
g_assert_cmpuint (g_list_length (list), ==, 3);
|
||||
g_assert_cmpstr (list->data, ==, "another");
|
||||
g_assert_cmpstr (list->next->data, ==, "second");
|
||||
g_assert_cmpstr (list->next->next->data, ==, "test");
|
||||
g_list_free (list);
|
||||
|
||||
json_object_unref (object);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
on_closed_get_problem (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar **result = (gchar **)user_data;
|
||||
g_assert (result != NULL);
|
||||
g_assert (*result == NULL);
|
||||
g_assert (problem != NULL);
|
||||
*result = g_strdup (problem);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
on_closed_set_flag (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gboolean *flag = (gboolean *)user_data;
|
||||
*flag = TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_control_get_close (CockpitTransport *transport,
|
||||
const gchar *command,
|
||||
const gchar *channel,
|
||||
JsonObject *options,
|
||||
GBytes *payload,
|
||||
gpointer user_data)
|
||||
{
|
||||
JsonObject **result = (JsonObject **)user_data;
|
||||
g_assert (command != NULL);
|
||||
|
||||
if (g_str_equal (command, "close"))
|
||||
{
|
||||
g_assert (result != NULL);
|
||||
if (*result == NULL)
|
||||
*result = json_object_ref (options);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const gchar *host;
|
||||
guint64 version;
|
||||
} InitProblem;
|
||||
|
||||
static void
|
||||
test_bridge_init_problem (gconstpointer user_data)
|
||||
{
|
||||
const InitProblem *fixture = user_data;
|
||||
CockpitTransport *transport;
|
||||
CockpitPipe *pipe;
|
||||
GBytes *bytes;
|
||||
gboolean closed = FALSE;
|
||||
JsonObject *input;
|
||||
|
||||
g_assert (fixture != NULL);
|
||||
|
||||
const gchar *argv[] = {
|
||||
BUILDDIR "/cockpit-bridge",
|
||||
NULL
|
||||
};
|
||||
|
||||
/* missing version will spawn an expected warning in subprocess */
|
||||
if (fixture->version == 0)
|
||||
cockpit_test_allow_warnings ();
|
||||
|
||||
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
|
||||
transport = cockpit_pipe_transport_new (pipe);
|
||||
g_object_unref (pipe);
|
||||
|
||||
/* First send the actual init message */
|
||||
input = json_object_new ();
|
||||
json_object_set_string_member (input, "command", "init");
|
||||
if (fixture->version != 0)
|
||||
json_object_set_int_member (input, "version", fixture->version);
|
||||
if (fixture->host)
|
||||
json_object_set_string_member (input, "host", fixture->host);
|
||||
bytes = cockpit_json_write_bytes (input);
|
||||
cockpit_transport_send (transport, NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
json_object_unref (input);
|
||||
|
||||
/* The bridge should terminate */
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
|
||||
while (!closed)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_test_reset_warnings ();
|
||||
|
||||
g_signal_handlers_disconnect_by_func (transport, on_closed_set_flag, &closed);
|
||||
|
||||
g_object_unref (transport);
|
||||
|
||||
/* Just checking that it closes by itself here */
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const gchar *host;
|
||||
const gchar *open_host;
|
||||
const gchar *problem;
|
||||
} OpenProblem;
|
||||
|
||||
static void
|
||||
test_bridge_open_problem (gconstpointer user_data)
|
||||
{
|
||||
const OpenProblem *fixture = user_data;
|
||||
CockpitTransport *transport;
|
||||
CockpitPipe *pipe;
|
||||
GBytes *bytes;
|
||||
JsonObject *object = NULL;
|
||||
JsonObject *input;
|
||||
|
||||
g_assert (fixture != NULL);
|
||||
g_assert (fixture->problem != NULL);
|
||||
|
||||
const gchar *argv[] = {
|
||||
BUILDDIR "/cockpit-bridge",
|
||||
NULL
|
||||
};
|
||||
|
||||
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
|
||||
transport = cockpit_pipe_transport_new (pipe);
|
||||
g_object_unref (pipe);
|
||||
|
||||
/* First send the actual init message */
|
||||
input = json_object_new ();
|
||||
json_object_set_string_member (input, "command", "init");
|
||||
json_object_set_int_member (input, "version", 1);
|
||||
if (fixture->host)
|
||||
json_object_set_string_member (input, "host", fixture->host);
|
||||
bytes = cockpit_json_write_bytes (input);
|
||||
cockpit_transport_send (transport, NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
json_object_unref (input);
|
||||
|
||||
/* Next maybe send an open message */
|
||||
input = json_object_new ();
|
||||
json_object_set_string_member (input, "command", "open");
|
||||
json_object_set_string_member (input, "channel", "444");
|
||||
json_object_set_string_member (input, "payload", "null");
|
||||
if (fixture->open_host)
|
||||
json_object_set_string_member (input, "host", fixture->open_host);
|
||||
bytes = cockpit_json_write_bytes (input);
|
||||
cockpit_transport_send (transport, NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
json_object_unref (input);
|
||||
|
||||
/* Listen for a close message */
|
||||
g_signal_connect (transport, "control", G_CALLBACK (on_control_get_close), &object);
|
||||
|
||||
while (object == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_signal_handlers_disconnect_by_func (transport, on_control_get_close, &object);
|
||||
|
||||
g_object_unref (transport);
|
||||
|
||||
g_assert_cmpstr (json_object_get_string_member (object, "problem"), ==, fixture->problem);
|
||||
json_object_unref (object);
|
||||
}
|
||||
|
||||
static InitProblem bad_version = {
|
||||
.version = 5,
|
||||
};
|
||||
|
||||
static InitProblem missing_version = {
|
||||
.version = 0,
|
||||
};
|
||||
|
||||
static InitProblem missing_host = {
|
||||
.version = 1,
|
||||
};
|
||||
|
||||
static OpenProblem wrong_host = {
|
||||
.host = "marmalade",
|
||||
.open_host = "juggs",
|
||||
.problem = "not-supported",
|
||||
};
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_setenv_check ("XDG_DATA_DIRS", SRCDIR "/src/bridge/mock-resource/system", TRUE);
|
||||
cockpit_setenv_check ("XDG_DATA_HOME", SRCDIR "/src/bridge/mock-resource/home", TRUE);
|
||||
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add_func ("/bridge/init-message", test_bridge_init);
|
||||
g_test_add_data_func ("/bridge/bad-version", &bad_version, test_bridge_init_problem);
|
||||
g_test_add_data_func ("/bridge/missing-version", &missing_version, test_bridge_init_problem);
|
||||
g_test_add_data_func ("/bridge/missing-host", &missing_host, test_bridge_init_problem);
|
||||
g_test_add_data_func ("/bridge/wrong-host", &wrong_host, test_bridge_open_problem);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,466 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitconnect.h"
|
||||
|
||||
#include "common/cockpitloopback.h"
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-unix.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <gio/gunixsocketaddress.h>
|
||||
#include <gio/gunixinputstream.h>
|
||||
#include <gio/gunixoutputstream.h>
|
||||
|
||||
#include <sys/uio.h>
|
||||
#include <string.h>
|
||||
|
||||
extern const gchar *cockpit_bridge_local_address;
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Mock
|
||||
*/
|
||||
|
||||
static GType mock_echo_channel_get_type (void) G_GNUC_CONST;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
gboolean close_called;
|
||||
} MockEchoChannel;
|
||||
|
||||
typedef CockpitChannelClass MockEchoChannelClass;
|
||||
|
||||
G_DEFINE_TYPE (MockEchoChannel, mock_echo_channel, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static void
|
||||
mock_echo_channel_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
cockpit_channel_send (channel, message, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
mock_echo_channel_control (CockpitChannel *channel,
|
||||
const gchar *command,
|
||||
JsonObject *options)
|
||||
{
|
||||
cockpit_channel_control (channel, command, options);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
mock_echo_channel_close (CockpitChannel *channel,
|
||||
const gchar *problem)
|
||||
{
|
||||
MockEchoChannel *self = (MockEchoChannel *)channel;
|
||||
self->close_called = TRUE;
|
||||
COCKPIT_CHANNEL_CLASS (mock_echo_channel_parent_class)->close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
mock_echo_channel_init (MockEchoChannel *self)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
mock_echo_channel_class_init (MockEchoChannelClass *klass)
|
||||
{
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
channel_class->recv = mock_echo_channel_recv;
|
||||
channel_class->control = mock_echo_channel_control;
|
||||
channel_class->close = mock_echo_channel_close;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Tests
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
GSocket *listen_sock;
|
||||
GSource *listen_source;
|
||||
GSocket *conn_sock;
|
||||
GSource *conn_source;
|
||||
GSocketAddress *address;
|
||||
gboolean skip_ipv6_loopback;
|
||||
guint16 port;
|
||||
} TestConnect;
|
||||
|
||||
static void
|
||||
on_ready_get_result (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GAsyncResult **retval = user_data;
|
||||
g_assert (retval != NULL);
|
||||
g_assert (*retval == NULL);
|
||||
*retval = g_object_ref (result);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_socket_input (GSocket *socket,
|
||||
GIOCondition cond,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar buffer[1024];
|
||||
GError *error = NULL;
|
||||
gssize ret, wret;
|
||||
|
||||
ret = g_socket_receive (socket, buffer, sizeof (buffer), NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
g_socket_shutdown (socket, FALSE, TRUE, &error);
|
||||
g_assert_no_error (error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_assert (ret > 0);
|
||||
wret = g_socket_send (socket, buffer, ret, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (wret == ret);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_socket_connection (GSocket *socket,
|
||||
GIOCondition cond,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestConnect *tc = user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (tc->conn_source == NULL);
|
||||
tc->conn_sock = g_socket_accept (tc->listen_sock, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
tc->conn_source = g_socket_create_source (tc->conn_sock, G_IO_IN, NULL);
|
||||
g_source_set_callback (tc->conn_source, (GSourceFunc)on_socket_input, tc, NULL);
|
||||
g_source_attach (tc->conn_source, NULL);
|
||||
|
||||
/* Only one connection */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
setup_connect (TestConnect *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GInetAddress *inet;
|
||||
GSocketAddress *address;
|
||||
GSocketFamily family = GPOINTER_TO_INT (data);
|
||||
|
||||
if (family == G_SOCKET_FAMILY_INVALID)
|
||||
family = G_SOCKET_FAMILY_IPV4;
|
||||
|
||||
inet = g_inet_address_new_loopback (family);
|
||||
address = g_inet_socket_address_new (inet, 0);
|
||||
g_object_unref (inet);
|
||||
|
||||
tc->listen_sock = g_socket_new (family, G_SOCKET_TYPE_STREAM,
|
||||
G_SOCKET_PROTOCOL_DEFAULT, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_socket_bind (tc->listen_sock, address, TRUE, &error);
|
||||
g_object_unref (address);
|
||||
|
||||
if (error != NULL && family == G_SOCKET_FAMILY_IPV6)
|
||||
{
|
||||
/* Some test runners don't have IPv6 loopback, strangely enough */
|
||||
g_clear_error (&error);
|
||||
tc->skip_ipv6_loopback = TRUE;
|
||||
return;
|
||||
}
|
||||
|
||||
g_assert_no_error (error);
|
||||
|
||||
tc->address = g_socket_get_local_address (tc->listen_sock, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
tc->port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (tc->address));
|
||||
|
||||
g_socket_listen (tc->listen_sock, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
tc->listen_source = g_socket_create_source (tc->listen_sock, G_IO_IN, NULL);
|
||||
g_source_set_callback (tc->listen_source, (GSourceFunc)on_socket_connection, tc, NULL);
|
||||
g_source_attach (tc->listen_source, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown_connect (TestConnect *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
if (tc->address)
|
||||
g_object_unref (tc->address);
|
||||
if (tc->conn_source)
|
||||
{
|
||||
g_source_destroy (tc->conn_source);
|
||||
g_source_unref (tc->conn_source);
|
||||
}
|
||||
if (tc->listen_source)
|
||||
{
|
||||
g_source_destroy (tc->listen_source);
|
||||
g_source_unref (tc->listen_source);
|
||||
}
|
||||
g_clear_object (&tc->listen_sock);
|
||||
g_clear_object (&tc->conn_sock);
|
||||
}
|
||||
|
||||
static void
|
||||
test_connect (TestConnect *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
GAsyncResult *result = NULL;
|
||||
GError *error = NULL;
|
||||
GIOStream *io;
|
||||
|
||||
cockpit_connect_stream (G_SOCKET_CONNECTABLE (tc->address), NULL, on_ready_get_result, &result);
|
||||
|
||||
while (result == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert (result != NULL);
|
||||
io = cockpit_connect_stream_finish (result, &error);
|
||||
g_assert_no_error (error);
|
||||
g_object_unref (result);
|
||||
g_assert (io != NULL);
|
||||
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_object_unref (io);
|
||||
}
|
||||
|
||||
static void
|
||||
test_connect_loopback (TestConnect *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
CockpitConnectable connectable = { 0 };
|
||||
GAsyncResult *result = NULL;
|
||||
GError *error = NULL;
|
||||
GIOStream *io;
|
||||
|
||||
if (tc->skip_ipv6_loopback)
|
||||
{
|
||||
g_test_skip ("no loopback for ipv6 found");
|
||||
return;
|
||||
}
|
||||
|
||||
connectable.address = cockpit_loopback_new (tc->port);
|
||||
cockpit_connect_stream_full (&connectable, NULL, on_ready_get_result, &result);
|
||||
g_object_unref (connectable.address);
|
||||
|
||||
while (result == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert (result != NULL);
|
||||
io = cockpit_connect_stream_finish (result, &error);
|
||||
g_assert_no_error (error);
|
||||
g_object_unref (result);
|
||||
g_assert (io != NULL);
|
||||
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_object_unref (io);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fail_not_found (void)
|
||||
{
|
||||
GAsyncResult *result = NULL;
|
||||
GSocketAddress *address;
|
||||
GError *error = NULL;
|
||||
GIOStream *io;
|
||||
|
||||
address = g_unix_socket_address_new ("/non-existent");
|
||||
cockpit_connect_stream (G_SOCKET_CONNECTABLE (address), NULL, on_ready_get_result, &result);
|
||||
g_object_unref (address);
|
||||
|
||||
while (result == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert (result != NULL);
|
||||
io = cockpit_connect_stream_finish (result, &error);
|
||||
g_object_unref (result);
|
||||
g_assert (io == NULL);
|
||||
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fail_access_denied (void)
|
||||
{
|
||||
GAsyncResult *result = NULL;
|
||||
GSocketAddress *address;
|
||||
GError *error = NULL;
|
||||
GIOStream *io;
|
||||
gchar *unix_path;
|
||||
gint fd;
|
||||
|
||||
if (geteuid () == 0)
|
||||
{
|
||||
g_test_skip ("running as root");
|
||||
return;
|
||||
}
|
||||
|
||||
unix_path = g_strdup ("/tmp/cockpit-test-XXXXXX.sock");
|
||||
fd = g_mkstemp (unix_path);
|
||||
g_assert_cmpint (fd, >=, 0);
|
||||
|
||||
address = g_unix_socket_address_new ("/non-existent");
|
||||
cockpit_connect_stream (G_SOCKET_CONNECTABLE (address), NULL, on_ready_get_result, &result);
|
||||
g_object_unref (address);
|
||||
|
||||
while (result == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert (result != NULL);
|
||||
io = cockpit_connect_stream_finish (result, &error);
|
||||
g_object_unref (result);
|
||||
g_assert (io == NULL);
|
||||
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
g_error_free (error);
|
||||
|
||||
unlink (unix_path);
|
||||
g_free (unix_path);
|
||||
close (fd);
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_port (void)
|
||||
{
|
||||
JsonObject *options;
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
CockpitConnectable *connectable;
|
||||
GSocketAddress *address;
|
||||
GInetAddress *expected_ip;
|
||||
GInetAddress *got_ip; // owned by address
|
||||
gchar *name = NULL;
|
||||
|
||||
expected_ip = g_inet_address_new_from_string (cockpit_bridge_local_address);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", 8090);
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
channel = g_object_new (mock_echo_channel_get_type (),
|
||||
"transport", transport,
|
||||
"id", "55",
|
||||
"options", options,
|
||||
NULL);
|
||||
json_object_unref (options);
|
||||
connectable = cockpit_connect_parse_stream (channel);
|
||||
g_assert (connectable != NULL);
|
||||
|
||||
address = cockpit_connect_parse_address (channel, &name);
|
||||
|
||||
g_assert (g_socket_address_get_family (address) == G_SOCKET_FAMILY_IPV4);
|
||||
g_assert_cmpint (g_inet_socket_address_get_port ((GInetSocketAddress *)address),
|
||||
==, 8090);
|
||||
got_ip = g_inet_socket_address_get_address ((GInetSocketAddress *)address);
|
||||
g_assert (g_inet_address_equal (got_ip, expected_ip));
|
||||
|
||||
g_assert_cmpint (connectable->local, ==, TRUE);
|
||||
|
||||
g_object_unref (channel);
|
||||
cockpit_connectable_unref (connectable);
|
||||
g_object_unref (transport);
|
||||
g_object_unref (address);
|
||||
g_object_unref (expected_ip);
|
||||
g_free (name);
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_address (void)
|
||||
{
|
||||
JsonObject *options;
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
CockpitConnectable *connectable;
|
||||
GSocketAddress *address;
|
||||
GInetAddress *expected_ip;
|
||||
GInetAddress *got_ip; // owned by address
|
||||
gchar *name = NULL;
|
||||
|
||||
expected_ip = g_inet_address_new_from_string ("10.1.1.1");
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_string_member (options, "address", "10.1.1.1");
|
||||
json_object_set_int_member (options, "port", 8090);
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
channel = g_object_new (mock_echo_channel_get_type (),
|
||||
"transport", transport,
|
||||
"id", "55",
|
||||
"options", options,
|
||||
NULL);
|
||||
json_object_unref (options);
|
||||
connectable = cockpit_connect_parse_stream (channel);
|
||||
g_assert (connectable != NULL);
|
||||
|
||||
address = cockpit_connect_parse_address (channel, &name);
|
||||
|
||||
g_assert (g_socket_address_get_family (address) == G_SOCKET_FAMILY_IPV4);
|
||||
g_assert_cmpint (g_inet_socket_address_get_port ((GInetSocketAddress *)address),
|
||||
==, 8090);
|
||||
got_ip = g_inet_socket_address_get_address ((GInetSocketAddress *)address);
|
||||
g_assert (g_inet_address_equal (got_ip, expected_ip));
|
||||
|
||||
g_assert_cmpint (connectable->local, ==, FALSE);
|
||||
|
||||
g_object_unref (channel);
|
||||
cockpit_connectable_unref (connectable);
|
||||
g_object_unref (transport);
|
||||
g_object_unref (address);
|
||||
g_object_unref (expected_ip);
|
||||
g_free (name);
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_bridge_local_address = "127.0.0.1";
|
||||
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/connect/simple", TestConnect, NULL,
|
||||
setup_connect, test_connect, teardown_connect);
|
||||
g_test_add ("/connect/loopback-ipv4", TestConnect, GINT_TO_POINTER (G_SOCKET_FAMILY_IPV4),
|
||||
setup_connect, test_connect_loopback, teardown_connect);
|
||||
g_test_add ("/connect/loopback-ipv6", TestConnect, GINT_TO_POINTER (G_SOCKET_FAMILY_IPV6),
|
||||
setup_connect, test_connect_loopback, teardown_connect);
|
||||
|
||||
g_test_add_func ("/connect/not-found", test_fail_not_found);
|
||||
g_test_add_func ("/connect/access-denied", test_fail_access_denied);
|
||||
|
||||
g_test_add_func ("/channel/parse-port", test_parse_port);
|
||||
g_test_add_func ("/channel/parse-address", test_parse_address);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,760 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusmeta.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
#include "testlib/cockpittest.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
const GDBusInterfaceInfo *iface;
|
||||
const gchar *result;
|
||||
} BuildFixture;
|
||||
|
||||
static const GDBusArgInfo janitor_method_say_what = {
|
||||
-1, (gchar *)"what", "s"
|
||||
};
|
||||
|
||||
static const GDBusArgInfo janitor_method_say_how = {
|
||||
-1, (gchar *)"how", "i",
|
||||
};
|
||||
|
||||
static const GDBusArgInfo *janitor_method_say_in[] = {
|
||||
&janitor_method_say_what,
|
||||
&janitor_method_say_how,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const GDBusArgInfo janitor_method_say_said = {
|
||||
-1, (gchar *)"said", "a{sv}",
|
||||
};
|
||||
|
||||
static const GDBusArgInfo *janitor_method_say_out[] = {
|
||||
&janitor_method_say_said,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const GDBusMethodInfo janitor_method_say = {
|
||||
-1, (gchar *)"Say",
|
||||
(GDBusArgInfo **) &janitor_method_say_in,
|
||||
(GDBusArgInfo **) &janitor_method_say_out,
|
||||
};
|
||||
|
||||
static const GDBusArgInfo janitor_method_mop_mess = {
|
||||
-1, (gchar *)"mess", "sa{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}a{sv}ssa{sv}a{sv}b"
|
||||
};
|
||||
|
||||
static const GDBusArgInfo *janitor_method_mop_out[] = {
|
||||
&janitor_method_mop_mess,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const GDBusMethodInfo janitor_method_mop = {
|
||||
-1, (gchar *)"Mop",
|
||||
(GDBusArgInfo **) NULL,
|
||||
(GDBusArgInfo **) &janitor_method_mop_out,
|
||||
};
|
||||
|
||||
static const GDBusMethodInfo *janitor_methods[] = {
|
||||
&janitor_method_say,
|
||||
&janitor_method_mop,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const GDBusArgInfo janitor_signal_oh_oh = {
|
||||
-1, (gchar *)"oh", "v",
|
||||
};
|
||||
|
||||
static const GDBusArgInfo janitor_signal_oh_marmalade = {
|
||||
-1, (gchar *)"marmalade", "v"
|
||||
};
|
||||
|
||||
static const GDBusArgInfo *janitor_signal_oh_args[] = {
|
||||
&janitor_signal_oh_oh,
|
||||
&janitor_signal_oh_marmalade,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const GDBusSignalInfo janitor_signal_oh = {
|
||||
-1, (gchar *)"Oh",
|
||||
(GDBusArgInfo **)&janitor_signal_oh_args
|
||||
};
|
||||
|
||||
static const GDBusSignalInfo janitor_signal_boom = {
|
||||
-1, (gchar *)"Oh",
|
||||
(GDBusArgInfo **)&janitor_signal_oh_args
|
||||
};
|
||||
|
||||
static const GDBusSignalInfo *janitor_signals[] = {
|
||||
&janitor_signal_oh,
|
||||
&janitor_signal_boom,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const GDBusPropertyInfo janitor_property_name = {
|
||||
-1, (gchar *)"Name", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE
|
||||
};
|
||||
|
||||
static const GDBusPropertyInfo janitor_property_habit = {
|
||||
-1, (gchar *)"Habit", "a{sv}", G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE
|
||||
};
|
||||
|
||||
static const GDBusPropertyInfo janitor_property_hidden = {
|
||||
-1, (gchar *)"Hidden", "b", G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE
|
||||
};
|
||||
|
||||
static const GDBusPropertyInfo *janitor_properties[] = {
|
||||
&janitor_property_name,
|
||||
&janitor_property_habit,
|
||||
&janitor_property_hidden,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const GDBusInterfaceInfo janitor_interface = {
|
||||
-1, "planet.express.Janitor",
|
||||
(GDBusMethodInfo **) &janitor_methods,
|
||||
(GDBusSignalInfo **) &janitor_signals,
|
||||
(GDBusPropertyInfo **) &janitor_properties,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const gchar janitor_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"Say\": {"
|
||||
"\"in\": [\"s\",\"i\"],"
|
||||
"\"out\":[\"a{sv}\"]"
|
||||
"},"
|
||||
"\"Mop\": {"
|
||||
"\"out\":[\"sa{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}a{sv}ssa{sv}a{sv}b\"]"
|
||||
"}"
|
||||
"},"
|
||||
"\"properties\": {"
|
||||
"\"Name\": {"
|
||||
"\"flags\": \"r\","
|
||||
"\"type\": \"s\""
|
||||
"},"
|
||||
"\"Habit\": {"
|
||||
"\"flags\": \"rw\","
|
||||
"\"type\": \"a{sv}\""
|
||||
"},"
|
||||
"\"Hidden\": {"
|
||||
"\"flags\": \"w\","
|
||||
"\"type\": \"b\""
|
||||
"}"
|
||||
"},"
|
||||
"\"signals\": {"
|
||||
"\"Oh\": {"
|
||||
"\"in\": [\"v\",\"v\"]"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const GDBusInterfaceInfo no_methods_interface = {
|
||||
-1, "planet.express.NoMethods",
|
||||
(GDBusMethodInfo **) NULL,
|
||||
(GDBusSignalInfo **) &janitor_signals,
|
||||
(GDBusPropertyInfo **) &janitor_properties,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const gchar no_methods_json[] = "{"
|
||||
"\"properties\": {"
|
||||
"\"Name\": {"
|
||||
"\"flags\": \"r\","
|
||||
"\"type\": \"s\""
|
||||
"},"
|
||||
"\"Habit\": {"
|
||||
"\"flags\": \"rw\","
|
||||
"\"type\": \"a{sv}\""
|
||||
"},"
|
||||
"\"Hidden\": {"
|
||||
"\"flags\": \"w\","
|
||||
"\"type\": \"b\""
|
||||
"}"
|
||||
"},"
|
||||
"\"signals\": {"
|
||||
"\"Oh\": {"
|
||||
"\"in\": [\"v\",\"v\"]"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const GDBusInterfaceInfo no_signals_interface = {
|
||||
-1, "planet.express.NoSignals",
|
||||
(GDBusMethodInfo **) &janitor_methods,
|
||||
(GDBusSignalInfo **) NULL,
|
||||
(GDBusPropertyInfo **) &janitor_properties,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const gchar no_signals_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"Say\": {"
|
||||
"\"in\": [\"s\",\"i\"],"
|
||||
"\"out\":[\"a{sv}\"]"
|
||||
"},"
|
||||
"\"Mop\": {"
|
||||
"\"out\":[\"sa{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}a{sv}ssa{sv}a{sv}b\"]"
|
||||
"}"
|
||||
"},"
|
||||
"\"properties\": {"
|
||||
"\"Name\": {"
|
||||
"\"flags\": \"r\","
|
||||
"\"type\": \"s\""
|
||||
"},"
|
||||
"\"Habit\": {"
|
||||
"\"flags\": \"rw\","
|
||||
"\"type\": \"a{sv}\""
|
||||
"},"
|
||||
"\"Hidden\": {"
|
||||
"\"flags\": \"w\","
|
||||
"\"type\": \"b\""
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const GDBusInterfaceInfo no_properties_interface = {
|
||||
-1, "planet.express.NoProperties",
|
||||
(GDBusMethodInfo **) &janitor_methods,
|
||||
(GDBusSignalInfo **) &janitor_signals,
|
||||
(GDBusPropertyInfo **) NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const gchar no_properties_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"Say\": {"
|
||||
"\"in\": [\"s\",\"i\"],"
|
||||
"\"out\":[\"a{sv}\"]"
|
||||
"},"
|
||||
"\"Mop\": {"
|
||||
"\"out\":[\"sa{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}a{sv}ssa{sv}a{sv}b\"]"
|
||||
"}"
|
||||
"},"
|
||||
"\"signals\": {"
|
||||
"\"Oh\": {"
|
||||
"\"in\": [\"v\",\"v\"]"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const BuildFixture build_janitor_fixture = {
|
||||
&janitor_interface,
|
||||
janitor_json
|
||||
};
|
||||
|
||||
static const BuildFixture build_no_methods_fixture = {
|
||||
&no_methods_interface,
|
||||
no_methods_json
|
||||
};
|
||||
|
||||
static const BuildFixture build_no_signals_fixture = {
|
||||
&no_signals_interface,
|
||||
no_signals_json
|
||||
};
|
||||
|
||||
static const BuildFixture build_no_properties_fixture = {
|
||||
&no_properties_interface,
|
||||
no_properties_json
|
||||
};
|
||||
|
||||
static void
|
||||
test_build (gconstpointer data)
|
||||
{
|
||||
const BuildFixture *fixture = data;
|
||||
JsonObject *object;
|
||||
|
||||
object = cockpit_dbus_meta_build ((GDBusInterfaceInfo *)fixture->iface);
|
||||
cockpit_assert_json_eq (object, fixture->result);
|
||||
json_object_unref (object);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_arg (GDBusArgInfo *one,
|
||||
GDBusArgInfo *two)
|
||||
{
|
||||
g_assert (one != NULL);
|
||||
g_assert (two != NULL);
|
||||
g_assert_cmpstr (one->signature, ==, two->signature);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_args (GDBusArgInfo **one,
|
||||
GDBusArgInfo **two)
|
||||
{
|
||||
if (one == NULL || two == NULL)
|
||||
{
|
||||
g_assert (one == NULL && two == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
while (*one != NULL && *two != NULL)
|
||||
{
|
||||
assert_equal_arg (*one, *two);
|
||||
one++;
|
||||
two++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_method (GDBusMethodInfo *one,
|
||||
GDBusMethodInfo *two)
|
||||
{
|
||||
g_assert (one != NULL);
|
||||
g_assert (two != NULL);
|
||||
g_assert_cmpstr (one->name, ==, two->name);
|
||||
assert_equal_args (one->in_args, two->in_args);
|
||||
assert_equal_args (one->out_args, two->out_args);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_methods (GDBusMethodInfo **one,
|
||||
GDBusMethodInfo **two)
|
||||
{
|
||||
if (one == NULL || two == NULL)
|
||||
{
|
||||
g_assert (one == NULL && two == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
while (*one != NULL && *two != NULL)
|
||||
{
|
||||
assert_equal_method (*one, *two);
|
||||
one++;
|
||||
two++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_signal (GDBusSignalInfo *one,
|
||||
GDBusSignalInfo *two)
|
||||
{
|
||||
g_assert (one != NULL);
|
||||
g_assert (two != NULL);
|
||||
g_assert_cmpstr (one->name, ==, two->name);
|
||||
assert_equal_args (one->args, two->args);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_signals (GDBusSignalInfo **one,
|
||||
GDBusSignalInfo **two)
|
||||
{
|
||||
if (one == NULL || two == NULL)
|
||||
{
|
||||
g_assert (one == NULL && two == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
while (*one != NULL && *two != NULL)
|
||||
{
|
||||
assert_equal_signal (*one, *two);
|
||||
one++;
|
||||
two++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_property (GDBusPropertyInfo *one,
|
||||
GDBusPropertyInfo *two)
|
||||
{
|
||||
g_assert (one != NULL);
|
||||
g_assert (two != NULL);
|
||||
g_assert_cmpstr (one->name, ==, two->name);
|
||||
g_assert_cmpstr (one->signature, ==, two->signature);
|
||||
g_assert_cmpuint (one->flags, ==, two->flags);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_properties (GDBusPropertyInfo **one,
|
||||
GDBusPropertyInfo **two)
|
||||
{
|
||||
if (one == NULL || two == NULL)
|
||||
{
|
||||
g_assert (one == NULL && two == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
while (*one != NULL && *two != NULL)
|
||||
{
|
||||
assert_equal_property (*one, *two);
|
||||
one++;
|
||||
two++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
assert_equal_interface (GDBusInterfaceInfo *one,
|
||||
GDBusInterfaceInfo *two)
|
||||
{
|
||||
g_assert (one != NULL);
|
||||
g_assert (two != NULL);
|
||||
g_assert_cmpstr (one->name, ==, two->name);
|
||||
assert_equal_methods (one->methods, two->methods);
|
||||
assert_equal_signals (one->signals, two->signals);
|
||||
assert_equal_properties (one->properties, two->properties);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const gchar *name;
|
||||
const gchar *input;
|
||||
const GDBusInterfaceInfo *iface;
|
||||
} ParseFixture;
|
||||
|
||||
static const ParseFixture parse_janitor_fixture = {
|
||||
"planet.express.Janitor",
|
||||
janitor_json,
|
||||
&janitor_interface
|
||||
};
|
||||
|
||||
static const ParseFixture parse_no_methods_fixture = {
|
||||
"planet.express.NoMethods",
|
||||
no_methods_json,
|
||||
&no_methods_interface
|
||||
};
|
||||
|
||||
static const ParseFixture parse_no_signals_fixture = {
|
||||
"planet.express.NoSignals",
|
||||
no_signals_json,
|
||||
&no_signals_interface
|
||||
};
|
||||
|
||||
static const ParseFixture parse_no_properties_fixture = {
|
||||
"planet.express.NoProperties",
|
||||
no_properties_json,
|
||||
&no_properties_interface
|
||||
};
|
||||
|
||||
static void
|
||||
test_parse (gconstpointer data)
|
||||
{
|
||||
const ParseFixture *fixture = data;
|
||||
GDBusInterfaceInfo *iface;
|
||||
GError *error = NULL;
|
||||
JsonObject *object;
|
||||
|
||||
object = cockpit_json_parse_object (fixture->input, -1, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
iface = cockpit_dbus_meta_parse (fixture->name, object, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
assert_equal_interface (iface, (GDBusInterfaceInfo *)fixture->iface);
|
||||
|
||||
g_dbus_interface_info_unref (iface);
|
||||
json_object_unref (object);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const gchar *input;
|
||||
const gchar *message;
|
||||
} ErrorFixture;
|
||||
|
||||
static const gchar invalid_in_argument_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"BrokenMethod\": {"
|
||||
"\"in\": [ true ]"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_invalid_in_argument = {
|
||||
invalid_in_argument_json,
|
||||
"invalid argument in dbus meta field"
|
||||
};
|
||||
|
||||
static const gchar invalid_out_argument_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"BrokenMethod\": {"
|
||||
"\"out\": [ true ]"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_invalid_out_argument = {
|
||||
invalid_out_argument_json,
|
||||
"invalid argument in dbus meta field"
|
||||
};
|
||||
|
||||
static const gchar invalid_signal_argument_json[] = "{"
|
||||
"\"signals\": {"
|
||||
"\"BrokenSignal\": {"
|
||||
"\"in\": [ true ]"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_invalid_signal_argument = {
|
||||
invalid_signal_argument_json,
|
||||
"invalid argument in dbus meta field"
|
||||
};
|
||||
|
||||
static const gchar invalid_signature_argument_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"BrokenMethod\": {"
|
||||
"\"in\": [\"s\",\"!!!\"]"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_signature_argument = {
|
||||
invalid_signature_argument_json,
|
||||
"argument in dbus meta field has invalid signature: !!!"
|
||||
};
|
||||
|
||||
static const gchar invalid_in_method_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"BrokenMethod\": {"
|
||||
"\"in\": true,"
|
||||
"\"out\":[\"a{sv}\"]"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_invalid_in_method = {
|
||||
invalid_in_method_json,
|
||||
"invalid \"in\" field in dbus meta method: BrokenMethod"
|
||||
};
|
||||
|
||||
static const gchar invalid_out_method_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"BrokenMethod\": {"
|
||||
"\"in\":[\"a{sv}\"],"
|
||||
"\"out\": 5"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_invalid_out_method = {
|
||||
invalid_out_method_json,
|
||||
"invalid \"out\" field in dbus meta method: BrokenMethod"
|
||||
};
|
||||
|
||||
static const gchar invalid_in_signal_json[] = "{"
|
||||
"\"signals\": {"
|
||||
"\"BrokenSignal\": {"
|
||||
"\"in\": { }"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_in_signal_fixture = {
|
||||
invalid_in_signal_json,
|
||||
"invalid \"in\" field in dbus meta signal: BrokenSignal"
|
||||
};
|
||||
|
||||
static const gchar invalid_flags_property_json[] = "{"
|
||||
"\"properties\": {"
|
||||
"\"BrokenProperty\": {"
|
||||
"\"flags\": [ ],"
|
||||
"\"type\": \"s\""
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_flags_property_fixture = {
|
||||
invalid_flags_property_json,
|
||||
"invalid \"flags\" field in dbus property: BrokenProperty"
|
||||
};
|
||||
|
||||
static const gchar invalid_type_property_json[] = "{"
|
||||
"\"properties\": {"
|
||||
"\"BrokenProperty\": {"
|
||||
"\"flags\": \"r\","
|
||||
"\"type\": 555"
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_type_property_fixture = {
|
||||
invalid_type_property_json,
|
||||
"invalid \"type\" field in dbus property: BrokenProperty"
|
||||
};
|
||||
|
||||
static const gchar missing_type_property_json[] = "{"
|
||||
"\"properties\": {"
|
||||
"\"BrokenProperty\": {"
|
||||
"\"flags\": \"r\""
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_type_missing_fixture = {
|
||||
missing_type_property_json,
|
||||
"missing \"type\" field in dbus property: BrokenProperty"
|
||||
};
|
||||
|
||||
static const gchar invalid_signature_property_json[] = "{"
|
||||
"\"properties\": {"
|
||||
"\"BrokenProperty\": {"
|
||||
"\"flags\": \"r\","
|
||||
"\"type\": \"???\""
|
||||
"}"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_signature_property_fixture = {
|
||||
invalid_signature_property_json,
|
||||
"the \"type\" field in dbus property is not a dbus signature: ???"
|
||||
};
|
||||
|
||||
static const gchar invalid_methods_json[] = "{"
|
||||
"\"methods\": [ ]"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_methods_fixture = {
|
||||
invalid_methods_json,
|
||||
"invalid \"methods\" field in dbus meta structure"
|
||||
};
|
||||
|
||||
static const gchar invalid_method_json[] = "{"
|
||||
"\"methods\": {"
|
||||
"\"BadMethod\": [ ]"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_method_json = {
|
||||
invalid_method_json,
|
||||
"invalid method field in dbus meta structure: BadMethod",
|
||||
};
|
||||
|
||||
static const gchar invalid_signals_json[] = "{"
|
||||
"\"signals\": 555"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_signals_json = {
|
||||
invalid_signals_json,
|
||||
"invalid \"signals\" field in dbus meta structure"
|
||||
};
|
||||
|
||||
static const gchar invalid_signal_json[] = "{"
|
||||
"\"signals\": {"
|
||||
"\"BadSignal\": true"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_signal_json = {
|
||||
invalid_signal_json,
|
||||
"invalid signal field in dbus meta structure: BadSignal",
|
||||
};
|
||||
|
||||
static const gchar invalid_properties_json[] = "{"
|
||||
"\"properties\": [ ]"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_properties_json = {
|
||||
invalid_properties_json,
|
||||
"invalid \"properties\" field in dbus meta structure"
|
||||
};
|
||||
|
||||
static const gchar invalid_property_json[] = "{"
|
||||
"\"properties\": {"
|
||||
"\"BadProperty\": true"
|
||||
"}"
|
||||
"}";
|
||||
|
||||
static const ErrorFixture error_property_json = {
|
||||
invalid_property_json,
|
||||
"invalid property field in dbus meta structure: BadProperty",
|
||||
};
|
||||
|
||||
static void
|
||||
test_error (gconstpointer data)
|
||||
{
|
||||
const ErrorFixture *fixture = data;
|
||||
GDBusInterfaceInfo *iface;
|
||||
GError *error = NULL;
|
||||
JsonObject *object;
|
||||
|
||||
object = cockpit_json_parse_object (fixture->input, -1, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
iface = cockpit_dbus_meta_parse ("name.not.Important", object, &error);
|
||||
g_assert (iface == NULL);
|
||||
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
|
||||
g_assert_cmpstr (error->message, ==, fixture->message);
|
||||
|
||||
g_error_free (error);
|
||||
json_object_unref (object);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add_data_func ("/dbus-meta/build/basic",
|
||||
&build_janitor_fixture, test_build);
|
||||
g_test_add_data_func ("/dbus-meta/build/no-methods",
|
||||
&build_no_methods_fixture, test_build);
|
||||
g_test_add_data_func ("/dbus-meta/build/no-signals",
|
||||
&build_no_signals_fixture, test_build);
|
||||
g_test_add_data_func ("/dbus-meta/build/no-properties",
|
||||
&build_no_properties_fixture, test_build);
|
||||
|
||||
g_test_add_data_func ("/dbus-meta/parse/basic",
|
||||
&parse_janitor_fixture, test_parse);
|
||||
g_test_add_data_func ("/dbus-meta/parse/no-methods",
|
||||
&parse_no_methods_fixture, test_parse);
|
||||
g_test_add_data_func ("/dbus-meta/parse/no-signals",
|
||||
&parse_no_signals_fixture, test_parse);
|
||||
g_test_add_data_func ("/dbus-meta/parse/no-properties",
|
||||
&parse_no_properties_fixture, test_parse);
|
||||
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-in-argument",
|
||||
&error_invalid_in_argument, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-out-argument",
|
||||
&error_invalid_out_argument, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-signal-argument",
|
||||
&error_invalid_signal_argument, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-signature-argument",
|
||||
&error_signature_argument, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-in-arguments",
|
||||
&error_invalid_in_method, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-out-arguments",
|
||||
&error_invalid_out_method, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-signal-arguments",
|
||||
&error_in_signal_fixture, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-property-flags",
|
||||
&error_flags_property_fixture, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-property-type",
|
||||
&error_type_property_fixture, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/missing-property-type",
|
||||
&error_type_missing_fixture, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-property-signature",
|
||||
&error_signature_property_fixture, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-methods",
|
||||
&error_methods_fixture, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-method",
|
||||
&error_method_json, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-signals",
|
||||
&error_signals_json, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-signal",
|
||||
&error_signal_json, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-properties",
|
||||
&error_properties_json, test_error);
|
||||
g_test_add_data_func ("/dbus-meta/parse/invalid-property",
|
||||
&error_property_json, test_error);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
1075
src/bridge/test-fs.c
1075
src/bridge/test-fs.c
File diff suppressed because it is too large
Load diff
|
|
@ -1,904 +0,0 @@
|
|||
|
||||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpithttpstream.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
#include "common/cockpitwebresponse.h"
|
||||
#include "common/cockpitwebserver.h"
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Declared in cockpitwebserver.c */
|
||||
extern gboolean cockpit_webserver_want_certificate;
|
||||
|
||||
/* JSON dict snippet for headers that are present in every request */
|
||||
#define STATIC_HEADERS "\"Cross-Origin-Resource-Policy\":\"same-origin\",\"Referrer-Policy\":\"no-referrer\",\"X-Content-Type-Options\":\"nosniff\",\"X-DNS-Prefetch-Control\":\"off\",\"X-Frame-Options\":\"sameorigin\""
|
||||
|
||||
static void
|
||||
on_closed_set_flag (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gboolean *flag = user_data;
|
||||
g_assert (flag != NULL);
|
||||
g_assert (*flag != TRUE);
|
||||
*flag = TRUE;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
CockpitWebServer *web_server;
|
||||
guint port;
|
||||
MockTransport *transport;
|
||||
const char *host;
|
||||
} TestGeneral;
|
||||
|
||||
|
||||
static gchar *
|
||||
non_local_ip (void)
|
||||
{
|
||||
GInetAddress *inet;
|
||||
gchar *str = NULL;
|
||||
inet = cockpit_test_find_non_loopback_address ();
|
||||
if (inet)
|
||||
{
|
||||
str = g_inet_address_to_string (inet);
|
||||
g_object_unref (inet);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static void
|
||||
setup_general (TestGeneral *tt,
|
||||
gconstpointer host_fixture)
|
||||
{
|
||||
tt->web_server = cockpit_web_server_new (NULL, COCKPIT_WEB_SERVER_NONE);
|
||||
tt->port = cockpit_web_server_add_inet_listener (tt->web_server, NULL, 0, NULL);
|
||||
cockpit_web_server_start (tt->web_server);
|
||||
tt->transport = mock_transport_new ();
|
||||
tt->host = host_fixture;
|
||||
}
|
||||
|
||||
static void
|
||||
teardown_general (TestGeneral *tt,
|
||||
gconstpointer unused)
|
||||
{
|
||||
g_object_unref (tt->web_server);
|
||||
g_object_unref (tt->transport);
|
||||
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_host_header (CockpitWebServer *server,
|
||||
CockpitWebRequest *request,
|
||||
const gchar *path,
|
||||
GHashTable *headers,
|
||||
CockpitWebResponse *response,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestGeneral *tt = user_data;
|
||||
const gchar *data = "Da Da Da";
|
||||
gchar *expected;
|
||||
GBytes *bytes;
|
||||
|
||||
expected = g_strdup_printf ("%s:%d", tt->host, tt->port);
|
||||
g_assert_cmpstr (g_hash_table_lookup (headers, "Host"), ==, expected);
|
||||
g_free (expected);
|
||||
|
||||
bytes = g_bytes_new_static (data, strlen (data));
|
||||
cockpit_web_response_content (response, NULL, bytes, NULL);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Compare subsequent "{ control JSON }" and "body" channel messages against expected values. */
|
||||
static void
|
||||
assert_channel_response (MockTransport *transport,
|
||||
const gchar *channel_id,
|
||||
const gchar *expected_control,
|
||||
const gchar *expected_body)
|
||||
{
|
||||
GBytes *data;
|
||||
|
||||
data = mock_transport_pop_channel (transport, channel_id);
|
||||
JsonNode *node = cockpit_json_parse (g_bytes_get_data (data, NULL), -1, NULL);
|
||||
cockpit_assert_json_eq (json_node_get_object (node), expected_control);
|
||||
json_node_unref (node);
|
||||
|
||||
data = mock_transport_pop_channel (transport, channel_id);
|
||||
cockpit_assert_bytes_eq (data, expected_body, -1);
|
||||
}
|
||||
|
||||
static void
|
||||
test_host_header (TestGeneral *tt,
|
||||
gconstpointer unused)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
GBytes *bytes;
|
||||
JsonObject *options;
|
||||
const gchar *control;
|
||||
gboolean closed;
|
||||
|
||||
if (tt->host == NULL)
|
||||
{
|
||||
g_test_skip ("Couldn't determine non local ip");
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_connect (tt->web_server, "handle-resource::/", G_CALLBACK (handle_host_header), tt);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", tt->port);
|
||||
json_object_set_string_member (options, "payload", "http-stream1");
|
||||
json_object_set_string_member (options, "method", "GET");
|
||||
json_object_set_string_member (options, "path", "/");
|
||||
|
||||
if (g_strcmp0 (tt->host, "localhost") != 0)
|
||||
json_object_set_string_member (options, "address", tt->host);
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_HTTP_STREAM,
|
||||
"transport", tt->transport,
|
||||
"id", "444",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
|
||||
/* Tell HTTP we have no more data to send */
|
||||
control = "{\"command\": \"done\", \"channel\": \"444\"}";
|
||||
bytes = g_bytes_new_static (control, strlen (control));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tt->transport), NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
closed = FALSE;
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
while (!closed)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
assert_channel_response (tt->transport, "444",
|
||||
"{\"status\":200,\"reason\":\"OK\",\"headers\":{" STATIC_HEADERS "}}",
|
||||
"Da Da Da");
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_default (CockpitWebServer *server,
|
||||
CockpitWebRequest *request,
|
||||
const gchar *path,
|
||||
GHashTable *headers,
|
||||
CockpitWebResponse *response,
|
||||
gpointer user_data)
|
||||
{
|
||||
const gchar *data = "Da Da Da";
|
||||
GBytes *bytes;
|
||||
|
||||
bytes = g_bytes_new_static (data, strlen (data));
|
||||
cockpit_web_response_content (response, NULL, bytes, NULL);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_http_stream2 (TestGeneral *tt,
|
||||
gconstpointer unused)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
GBytes *bytes;
|
||||
JsonObject *options;
|
||||
const gchar *control;
|
||||
JsonObject *object;
|
||||
gboolean closed;
|
||||
GBytes *data;
|
||||
guint count;
|
||||
|
||||
g_signal_connect (tt->web_server, "handle-resource::/", G_CALLBACK (handle_default), tt);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", tt->port);
|
||||
json_object_set_string_member (options, "payload", "http-stream2");
|
||||
json_object_set_string_member (options, "method", "GET");
|
||||
json_object_set_string_member (options, "path", "/");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_HTTP_STREAM,
|
||||
"transport", tt->transport,
|
||||
"id", "444",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
|
||||
/* Tell HTTP we have no more data to send */
|
||||
control = "{\"command\": \"done\", \"channel\": \"444\"}";
|
||||
bytes = g_bytes_new_static (control, strlen (control));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tt->transport), NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
closed = FALSE;
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
while (!closed)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
object = mock_transport_pop_control (tt->transport);
|
||||
cockpit_assert_json_eq (object, "{\"command\":\"ready\",\"channel\":\"444\"}");
|
||||
object = mock_transport_pop_control (tt->transport);
|
||||
cockpit_assert_json_eq (object, "{\"command\":\"response\",\"channel\":\"444\",\"status\":200,\"reason\":\"OK\",\"headers\":{" STATIC_HEADERS "}}");
|
||||
|
||||
data = mock_transport_combine_output (tt->transport, "444", &count);
|
||||
cockpit_assert_bytes_eq (data, "Da Da Da", -1);
|
||||
g_assert_cmpuint (count, ==, 1);
|
||||
g_bytes_unref (data);
|
||||
}
|
||||
|
||||
static void
|
||||
test_cannot_connect (TestGeneral *tt,
|
||||
gconstpointer unused)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
GBytes *bytes;
|
||||
JsonObject *options;
|
||||
const gchar *control;
|
||||
JsonObject *object;
|
||||
gboolean closed;
|
||||
|
||||
cockpit_expect_log ("cockpit-bridge", G_LOG_LEVEL_MESSAGE, "*couldn't connect*");
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", 5555);
|
||||
json_object_set_string_member (options, "payload", "http-stream2");
|
||||
json_object_set_string_member (options, "method", "GET");
|
||||
json_object_set_string_member (options, "path", "/");
|
||||
json_object_set_string_member (options, "address", "0.0.0.0");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_HTTP_STREAM,
|
||||
"transport", tt->transport,
|
||||
"id", "444",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
|
||||
/* Tell HTTP we have no more data to send */
|
||||
control = "{\"command\": \"done\", \"channel\": \"444\"}";
|
||||
bytes = g_bytes_new_static (control, strlen (control));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tt->transport), NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
closed = FALSE;
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
while (!closed)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
object = mock_transport_pop_control (tt->transport);
|
||||
cockpit_assert_json_eq (object, "{\"command\":\"close\",\"channel\":\"444\",\"problem\":\"not-found\"}");
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Test
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
gchar *problem;
|
||||
gboolean done;
|
||||
} TestResult;
|
||||
|
||||
/*
|
||||
* Yes this is a magic number. It's the lowest number that would
|
||||
* trigger a bug where chunked data would be rejected due to an incomplete read.
|
||||
*/
|
||||
const gint MAGIC_NUMBER = 3068;
|
||||
|
||||
static gboolean
|
||||
handle_chunked (CockpitWebServer *server,
|
||||
CockpitWebRequest *request,
|
||||
const gchar *path,
|
||||
GHashTable *headers,
|
||||
CockpitWebResponse *response,
|
||||
gpointer user_data)
|
||||
{
|
||||
GBytes *bytes;
|
||||
GHashTable *h = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
|
||||
cockpit_web_response_headers_full (response, 200,
|
||||
"OK", -1, h);
|
||||
bytes = g_bytes_new_take (g_strdup_printf ("%0*d",
|
||||
MAGIC_NUMBER, 0),
|
||||
MAGIC_NUMBER);
|
||||
cockpit_web_response_queue (response, bytes);
|
||||
cockpit_web_response_complete (response);
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
g_hash_table_unref (h);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_channel_close (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestResult *tr = user_data;
|
||||
g_assert (tr->done == FALSE);
|
||||
tr->done = TRUE;
|
||||
tr->problem = g_strdup (problem);
|
||||
}
|
||||
|
||||
static void
|
||||
on_transport_closed (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_http_chunked (void)
|
||||
{
|
||||
MockTransport *transport = NULL;
|
||||
CockpitChannel *channel = NULL;
|
||||
CockpitWebServer *web_server = NULL;
|
||||
JsonObject *options = NULL;
|
||||
JsonObject *headers = NULL;
|
||||
TestResult *tr = g_slice_new (TestResult);
|
||||
|
||||
GBytes *bytes = NULL;
|
||||
|
||||
const gchar *control;
|
||||
|
||||
web_server = cockpit_web_server_new (NULL, COCKPIT_WEB_SERVER_NONE);
|
||||
guint16 port = cockpit_web_server_add_inet_listener (web_server, NULL, 0, NULL);
|
||||
g_assert (port != 0);
|
||||
g_signal_connect (web_server, "handle-resource::/",
|
||||
G_CALLBACK (handle_chunked), NULL);
|
||||
|
||||
cockpit_web_server_start (web_server);
|
||||
|
||||
transport = mock_transport_new ();
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", port);
|
||||
json_object_set_string_member (options, "payload", "http-stream1");
|
||||
json_object_set_string_member (options, "method", "GET");
|
||||
json_object_set_string_member (options, "path", "/");
|
||||
|
||||
headers = json_object_new ();
|
||||
json_object_set_string_member (headers, "Pragma", "no-cache");
|
||||
json_object_set_object_member (options, "headers", headers);
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_HTTP_STREAM,
|
||||
"transport", transport,
|
||||
"id", "444",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
|
||||
/* Tell HTTP we have no more data to send */
|
||||
control = "{\"command\": \"done\", \"channel\": \"444\"}";
|
||||
bytes = g_bytes_new_static (control, strlen (control));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (transport), NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
tr->done = FALSE;
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_channel_close), tr);
|
||||
|
||||
while (tr->done == FALSE)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert_cmpstr (tr->problem, ==, NULL);
|
||||
|
||||
|
||||
|
||||
g_autofree gchar *expected_body = g_strdup_printf ("%0*d", MAGIC_NUMBER, 0);
|
||||
|
||||
assert_channel_response (transport, "444",
|
||||
"{\"status\":200,\"reason\":\"OK\",\"headers\":{" STATIC_HEADERS "}}",
|
||||
expected_body);
|
||||
|
||||
g_object_unref (transport);
|
||||
g_object_add_weak_pointer (G_OBJECT (channel), (gpointer *)&channel);
|
||||
g_object_unref (channel);
|
||||
g_assert (channel == NULL);
|
||||
g_clear_object (&web_server);
|
||||
|
||||
g_free (tr->problem);
|
||||
g_slice_free (TestResult, tr);
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_keep_alive (void)
|
||||
{
|
||||
const gchar *version;
|
||||
GHashTable *headers;
|
||||
gboolean keep_alive;
|
||||
|
||||
headers = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
|
||||
version = "HTTP/1.1";
|
||||
g_hash_table_insert (headers, "Connection", "keep-alive");
|
||||
|
||||
keep_alive = cockpit_http_stream_parse_keep_alive (version, headers);
|
||||
g_assert (keep_alive == TRUE);
|
||||
|
||||
version = "HTTP/1.0";
|
||||
keep_alive = cockpit_http_stream_parse_keep_alive (version, headers);
|
||||
g_assert (keep_alive == TRUE);
|
||||
|
||||
|
||||
g_hash_table_remove (headers, "Connection");
|
||||
|
||||
keep_alive = cockpit_http_stream_parse_keep_alive (version, headers);
|
||||
g_assert (keep_alive == FALSE);
|
||||
|
||||
version = "HTTP/1.1";
|
||||
keep_alive = cockpit_http_stream_parse_keep_alive (version, headers);
|
||||
g_assert (keep_alive == TRUE);
|
||||
|
||||
g_hash_table_destroy (headers);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GTlsCertificate *certificate;
|
||||
CockpitWebServer *web_server;
|
||||
guint port;
|
||||
MockTransport *transport;
|
||||
GTlsCertificate *peer;
|
||||
} TestTls;
|
||||
|
||||
static gboolean
|
||||
handle_test (CockpitWebServer *server,
|
||||
CockpitWebRequest *request,
|
||||
const gchar *path,
|
||||
GHashTable *headers,
|
||||
CockpitWebResponse *response,
|
||||
gpointer user_data)
|
||||
{
|
||||
const gchar *data = "Oh Marmalaade!";
|
||||
GTlsConnection *connection;
|
||||
TestTls *test = user_data;
|
||||
GBytes *bytes;
|
||||
|
||||
bytes = g_bytes_new_static (data, strlen (data));
|
||||
cockpit_web_response_content (response, NULL, bytes, NULL);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
connection = G_TLS_CONNECTION (cockpit_web_response_get_stream (response));
|
||||
|
||||
g_clear_object (&test->peer);
|
||||
test->peer = g_tls_connection_get_peer_certificate (connection);
|
||||
if (test->peer)
|
||||
g_object_ref (test->peer);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
setup_tls (TestTls *test,
|
||||
gconstpointer data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
/* don't require system SSL cert database in build environments */
|
||||
cockpit_expect_possible_log ("GLib-Net", G_LOG_LEVEL_WARNING, "couldn't load TLS file database: * No such file or directory");
|
||||
|
||||
test->certificate = g_tls_certificate_new_from_files (SRCDIR "/src/bridge/mock-server.crt",
|
||||
SRCDIR "/src/bridge/mock-server.key", &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
test->web_server = cockpit_web_server_new (test->certificate, COCKPIT_WEB_SERVER_NONE);
|
||||
test->port = cockpit_web_server_add_inet_listener (test->web_server, NULL, 0, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (test->port != 0);
|
||||
|
||||
g_signal_connect (test->web_server, "handle-resource::/test", G_CALLBACK (handle_test), test);
|
||||
|
||||
cockpit_web_server_start (test->web_server);
|
||||
|
||||
test->transport = mock_transport_new ();
|
||||
g_signal_connect (test->transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown_tls (TestTls *test,
|
||||
gconstpointer data)
|
||||
{
|
||||
g_object_unref (test->certificate);
|
||||
g_object_unref (test->web_server);
|
||||
g_object_unref (test->transport);
|
||||
g_clear_object (&test->peer);
|
||||
cockpit_webserver_want_certificate = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_tls_basic (TestTls *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
gboolean closed = FALSE;
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
const gchar *control;
|
||||
GBytes *bytes;
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", test->port);
|
||||
json_object_set_string_member (options, "payload", "http-stream1");
|
||||
json_object_set_string_member (options, "method", "GET");
|
||||
json_object_set_string_member (options, "path", "/test");
|
||||
json_object_set_object_member (options, "tls", json_object_new ());
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_HTTP_STREAM,
|
||||
"transport", test->transport,
|
||||
"id", "444",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
|
||||
/* Tell HTTP we have no more data to send */
|
||||
control = "{\"command\": \"done\", \"channel\": \"444\"}";
|
||||
bytes = g_bytes_new_static (control, strlen (control));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (test->transport), NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
|
||||
while (closed == FALSE)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
assert_channel_response (test->transport, "444",
|
||||
"{\"status\":200,\"reason\":\"OK\",\"headers\":{" STATIC_HEADERS "}}",
|
||||
"Oh Marmalaade!");
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (channel), (gpointer *)&channel);
|
||||
g_object_unref (channel);
|
||||
g_assert (channel == NULL);
|
||||
}
|
||||
|
||||
static const gchar fixture_tls_certificate_data[] =
|
||||
"{ \"certificate\": { \"data\": "
|
||||
"\"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICxzCCAa+gAwIBAgIJANDrBNw3XYJ0MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\n"
|
||||
"BAMMCWxvY2FsaG9zdDAgFw0xNTAzMjUxMDMzMzRaGA8yMTE1MDMwMTEwMzMzNFow\n"
|
||||
"FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
|
||||
"CgKCAQEA8l1q01B5N/biaFDazUtuPuOrFsLOC67LX1iiE62guchEf9FyEagglGzt\n"
|
||||
"XOSCpY/qX0HWmIkE3Pqotb8lPQ0mUHleYCvzY85cFmj4mu+rDIPxK/lw37Xu00iP\n"
|
||||
"/rbcCA6K6dgMjp0TJzZvMnU2PywtFqDpw6ZchcMi517keMfLwscUC/7Y80lP0PGA\n"
|
||||
"1wTDaYoxuMlUhqTTfdLoBZ73eA9YzgqBeZ9ePxoUFk9AtJtlOlR60mGbEOweDUfc\n"
|
||||
"l1biKtarDW5SJYbVTFjWdPsCV6czZndfVKAAkDd+bsbFMcEiq/doHU092Yy3sZ9g\n"
|
||||
"hnOBw5sCq8iTXQ9cmejxUrsu/SvL3QIDAQABoxowGDAJBgNVHRMEAjAAMAsGA1Ud\n"
|
||||
"DwQEAwIF4DANBgkqhkiG9w0BAQsFAAOCAQEAalykXV+z1tQOv1ZRvJmppjEIYTa3\n"
|
||||
"pFehy97BiNGERTQJQDSzOgptIaCJb1vE34KNL349QEO4F8XTPWhwsCAXNTBN4yhm\n"
|
||||
"NJ6qbYkz0HbBmdM4k0MgbB9VG00Hy+TmwEt0zVryICZY4IomKmS1No0Lai5hOqdz\n"
|
||||
"afUMVIIYjVB1WYIsIaXXug7Mik/O+6K5hIbqm9HkwRwfoVaOLNG9EPUM14vFnN5p\n"
|
||||
"EyHSBByk0mOU8EUK/qsAnbTwABEKsMxCopmvPTguGHTwllEvxPgt5BcYMU9oXlvc\n"
|
||||
"cSvnU4a6M2qxQn3LUqxENh9QaQ8vV4l/avZBi1cFKVs1rza36eOGxrJxQw==\n"
|
||||
"-----END CERTIFICATE-----\""
|
||||
"}, \"key\": { \"data\": "
|
||||
"\"-----BEGIN PRIVATE KEY-----\n"
|
||||
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDyXWrTUHk39uJo\n"
|
||||
"UNrNS24+46sWws4LrstfWKITraC5yER/0XIRqCCUbO1c5IKlj+pfQdaYiQTc+qi1\n"
|
||||
"vyU9DSZQeV5gK/NjzlwWaPia76sMg/Er+XDfte7TSI/+ttwIDorp2AyOnRMnNm8y\n"
|
||||
"dTY/LC0WoOnDplyFwyLnXuR4x8vCxxQL/tjzSU/Q8YDXBMNpijG4yVSGpNN90ugF\n"
|
||||
"nvd4D1jOCoF5n14/GhQWT0C0m2U6VHrSYZsQ7B4NR9yXVuIq1qsNblIlhtVMWNZ0\n"
|
||||
"+wJXpzNmd19UoACQN35uxsUxwSKr92gdTT3ZjLexn2CGc4HDmwKryJNdD1yZ6PFS\n"
|
||||
"uy79K8vdAgMBAAECggEAILEJH8fTEgFzOK7vVJHAJSuAgGl2cYz6Uboa4pyg+W5S\n"
|
||||
"DwupX0hWXK70tXr9RGfNLVwsHhcdWNFWwG0wELQdXu2AFWjYQ7YqJbuzDPMXF3EU\n"
|
||||
"ruHOn95igI1hHvJ7a3rKshA6YWI+myN0jFHTJ2JGEq9R2Nov0LspkhvypXgNvA/r\n"
|
||||
"JfFZ9IsPJZDWCnGXkPLlW2X1XEXw2BPs8ib+ZkbzGNiLsy/i4M/oA+g6lz4LU/ll\n"
|
||||
"J6cLhwPrBu02+PJt7MaYaNk5zqhyJs0AMjeBlNnXFIWAlTrIe/h8z/gL8ABrYWAA\n"
|
||||
"1kgZ11GO8bNAEfLOIUrA1/vq9aK00WDwFLXWJdVE4QKBgQD+R/J+AbYSImeoAj/3\n"
|
||||
"hfsFkaUNLyw1ZEO4LG2id0dnve1paL6Y/uXKKqxq0jiyMLT243Vi+1fzth7RNXOl\n"
|
||||
"ui0nnVWO7x68FsYcdIM7w+tryh2Y+UhCfwNCakM0GTohcXqFUEzHcwuOv8hAfRQ5\n"
|
||||
"jPBCwJdUHpIimVOo5/WRbQGW+wKBgQD0ANkof+jagdNqOkCvFnTPiFlPYrpDzeU5\n"
|
||||
"ZxhLlVxnr6G2MPoUO0IqTWVA7uCn29i0yUUXAtRHrkNI1EtKXRIUe2bChVegTBHx\n"
|
||||
"26PqXEOonSUJdpUzyzXVX2vSqICm0tTbqyZ0GbjP4y5qQOQHdTGFsHDfSTa5//P+\n"
|
||||
"0BLpci4RBwKBgQDBR8DrxLM3b41o6GTk6aNXpVBXCC9LWi4bVTH0l0PgeD54rBSM\n"
|
||||
"SNwz4mHyRF6yG1HChDybAz/kUN912HJSW4StIuuA3QN4prrpsCp8iDxvT09WEs25\n"
|
||||
"NcAtgIYamL5V42Lk6Jej1y/GzsIROsHfyOBrbObaGu6re+5aag5//uKBdwKBgQDp\n"
|
||||
"i4ZPBV7TBkBdBLS04UGdAly5Zz3xeDlW4B6Y+bUgaTLXN7mlc7K42qt3oyzUfdDF\n"
|
||||
"+X9vrv2QPnOYWdpWqw6LHDIXLZnZi/YBEMGrp/P6h67Th/T3RiGYwWRqlW3OPy4N\n"
|
||||
"s5tytMv37vKWMNYRbVKhK2hdz63aCep4kqAHYYpGMQKBgF83LTyRFwGFos/wDrgY\n"
|
||||
"eieLiipmdXGvlrBq6SBzKglIYwNRSGiWkXAuHRzD/2S546ioQKZr7AKuijKGdLMz\n"
|
||||
"ABVl/bqqqRXSDbvf+XEdU2rJpxhYWxlsJZMFBFIwuxR2jRqmCgbCvoZQcbIr1ZLr\n"
|
||||
"02eC2pQ5eio2+CKqBfqxbnwk\n"
|
||||
"-----END PRIVATE KEY-----\""
|
||||
" } }";
|
||||
|
||||
static const gchar fixture_tls_certificate_file[] =
|
||||
"{ \"certificate\": { \"file\": \"" SRCDIR "/src/bridge/mock-client.crt\" },"
|
||||
"\"key\": { \"file\": \"" SRCDIR "/src/bridge/mock-client.key\" } }";
|
||||
|
||||
static const gchar fixture_tls_certificate_data_file[] =
|
||||
"{ \"certificate\": { \"data\": "
|
||||
"\"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICxzCCAa+gAwIBAgIJANDrBNw3XYJ0MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\n"
|
||||
"BAMMCWxvY2FsaG9zdDAgFw0xNTAzMjUxMDMzMzRaGA8yMTE1MDMwMTEwMzMzNFow\n"
|
||||
"FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
|
||||
"CgKCAQEA8l1q01B5N/biaFDazUtuPuOrFsLOC67LX1iiE62guchEf9FyEagglGzt\n"
|
||||
"XOSCpY/qX0HWmIkE3Pqotb8lPQ0mUHleYCvzY85cFmj4mu+rDIPxK/lw37Xu00iP\n"
|
||||
"/rbcCA6K6dgMjp0TJzZvMnU2PywtFqDpw6ZchcMi517keMfLwscUC/7Y80lP0PGA\n"
|
||||
"1wTDaYoxuMlUhqTTfdLoBZ73eA9YzgqBeZ9ePxoUFk9AtJtlOlR60mGbEOweDUfc\n"
|
||||
"l1biKtarDW5SJYbVTFjWdPsCV6czZndfVKAAkDd+bsbFMcEiq/doHU092Yy3sZ9g\n"
|
||||
"hnOBw5sCq8iTXQ9cmejxUrsu/SvL3QIDAQABoxowGDAJBgNVHRMEAjAAMAsGA1Ud\n"
|
||||
"DwQEAwIF4DANBgkqhkiG9w0BAQsFAAOCAQEAalykXV+z1tQOv1ZRvJmppjEIYTa3\n"
|
||||
"pFehy97BiNGERTQJQDSzOgptIaCJb1vE34KNL349QEO4F8XTPWhwsCAXNTBN4yhm\n"
|
||||
"NJ6qbYkz0HbBmdM4k0MgbB9VG00Hy+TmwEt0zVryICZY4IomKmS1No0Lai5hOqdz\n"
|
||||
"afUMVIIYjVB1WYIsIaXXug7Mik/O+6K5hIbqm9HkwRwfoVaOLNG9EPUM14vFnN5p\n"
|
||||
"EyHSBByk0mOU8EUK/qsAnbTwABEKsMxCopmvPTguGHTwllEvxPgt5BcYMU9oXlvc\n"
|
||||
"cSvnU4a6M2qxQn3LUqxENh9QaQ8vV4l/avZBi1cFKVs1rza36eOGxrJxQw==\n"
|
||||
"-----END CERTIFICATE-----\""
|
||||
"}, \"key\": { \"file\": \"" SRCDIR "/src/bridge/mock-client.key\""
|
||||
"} }";
|
||||
|
||||
static const gchar fixture_tls_certificate_file_data[] =
|
||||
"{ \"certificate\": { \"file\": \"" SRCDIR "/src/bridge/mock-client.crt\""
|
||||
"}, \"key\": { \"data\": "
|
||||
"\"-----BEGIN PRIVATE KEY-----\n"
|
||||
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDyXWrTUHk39uJo\n"
|
||||
"UNrNS24+46sWws4LrstfWKITraC5yER/0XIRqCCUbO1c5IKlj+pfQdaYiQTc+qi1\n"
|
||||
"vyU9DSZQeV5gK/NjzlwWaPia76sMg/Er+XDfte7TSI/+ttwIDorp2AyOnRMnNm8y\n"
|
||||
"dTY/LC0WoOnDplyFwyLnXuR4x8vCxxQL/tjzSU/Q8YDXBMNpijG4yVSGpNN90ugF\n"
|
||||
"nvd4D1jOCoF5n14/GhQWT0C0m2U6VHrSYZsQ7B4NR9yXVuIq1qsNblIlhtVMWNZ0\n"
|
||||
"+wJXpzNmd19UoACQN35uxsUxwSKr92gdTT3ZjLexn2CGc4HDmwKryJNdD1yZ6PFS\n"
|
||||
"uy79K8vdAgMBAAECggEAILEJH8fTEgFzOK7vVJHAJSuAgGl2cYz6Uboa4pyg+W5S\n"
|
||||
"DwupX0hWXK70tXr9RGfNLVwsHhcdWNFWwG0wELQdXu2AFWjYQ7YqJbuzDPMXF3EU\n"
|
||||
"ruHOn95igI1hHvJ7a3rKshA6YWI+myN0jFHTJ2JGEq9R2Nov0LspkhvypXgNvA/r\n"
|
||||
"JfFZ9IsPJZDWCnGXkPLlW2X1XEXw2BPs8ib+ZkbzGNiLsy/i4M/oA+g6lz4LU/ll\n"
|
||||
"J6cLhwPrBu02+PJt7MaYaNk5zqhyJs0AMjeBlNnXFIWAlTrIe/h8z/gL8ABrYWAA\n"
|
||||
"1kgZ11GO8bNAEfLOIUrA1/vq9aK00WDwFLXWJdVE4QKBgQD+R/J+AbYSImeoAj/3\n"
|
||||
"hfsFkaUNLyw1ZEO4LG2id0dnve1paL6Y/uXKKqxq0jiyMLT243Vi+1fzth7RNXOl\n"
|
||||
"ui0nnVWO7x68FsYcdIM7w+tryh2Y+UhCfwNCakM0GTohcXqFUEzHcwuOv8hAfRQ5\n"
|
||||
"jPBCwJdUHpIimVOo5/WRbQGW+wKBgQD0ANkof+jagdNqOkCvFnTPiFlPYrpDzeU5\n"
|
||||
"ZxhLlVxnr6G2MPoUO0IqTWVA7uCn29i0yUUXAtRHrkNI1EtKXRIUe2bChVegTBHx\n"
|
||||
"26PqXEOonSUJdpUzyzXVX2vSqICm0tTbqyZ0GbjP4y5qQOQHdTGFsHDfSTa5//P+\n"
|
||||
"0BLpci4RBwKBgQDBR8DrxLM3b41o6GTk6aNXpVBXCC9LWi4bVTH0l0PgeD54rBSM\n"
|
||||
"SNwz4mHyRF6yG1HChDybAz/kUN912HJSW4StIuuA3QN4prrpsCp8iDxvT09WEs25\n"
|
||||
"NcAtgIYamL5V42Lk6Jej1y/GzsIROsHfyOBrbObaGu6re+5aag5//uKBdwKBgQDp\n"
|
||||
"i4ZPBV7TBkBdBLS04UGdAly5Zz3xeDlW4B6Y+bUgaTLXN7mlc7K42qt3oyzUfdDF\n"
|
||||
"+X9vrv2QPnOYWdpWqw6LHDIXLZnZi/YBEMGrp/P6h67Th/T3RiGYwWRqlW3OPy4N\n"
|
||||
"s5tytMv37vKWMNYRbVKhK2hdz63aCep4kqAHYYpGMQKBgF83LTyRFwGFos/wDrgY\n"
|
||||
"eieLiipmdXGvlrBq6SBzKglIYwNRSGiWkXAuHRzD/2S546ioQKZr7AKuijKGdLMz\n"
|
||||
"ABVl/bqqqRXSDbvf+XEdU2rJpxhYWxlsJZMFBFIwuxR2jRqmCgbCvoZQcbIr1ZLr\n"
|
||||
"02eC2pQ5eio2+CKqBfqxbnwk\n"
|
||||
"-----END PRIVATE KEY-----\""
|
||||
" } }";
|
||||
|
||||
static void
|
||||
test_tls_certificate (TestTls *test,
|
||||
gconstpointer json)
|
||||
{
|
||||
gboolean closed = FALSE;
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
JsonObject *tls;
|
||||
GError *error = NULL;
|
||||
GTlsCertificate *cert;
|
||||
const gchar *control;
|
||||
GBytes *bytes;
|
||||
|
||||
/* tell server to request client cert */
|
||||
cockpit_webserver_want_certificate = TRUE;
|
||||
|
||||
/* this happens twice, so once more in addition to the ignore in setup_tls */
|
||||
cockpit_expect_possible_log ("GLib-Net", G_LOG_LEVEL_WARNING, "couldn't load TLS file database: * No such file or directory");
|
||||
|
||||
tls = cockpit_json_parse_object (json, -1, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", test->port);
|
||||
json_object_set_string_member (options, "payload", "http-stream1");
|
||||
json_object_set_string_member (options, "method", "GET");
|
||||
json_object_set_string_member (options, "path", "/test");
|
||||
json_object_set_object_member (options, "tls", tls);
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_HTTP_STREAM,
|
||||
"transport", test->transport,
|
||||
"id", "444",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
|
||||
/* Tell HTTP we have no more data to send */
|
||||
control = "{\"command\": \"done\", \"channel\": \"444\"}";
|
||||
bytes = g_bytes_new_static (control, strlen (control));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (test->transport), NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
|
||||
while (closed == FALSE)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
assert_channel_response (test->transport, "444",
|
||||
"{\"status\":200,\"reason\":\"OK\",\"headers\":{" STATIC_HEADERS "}}",
|
||||
"Oh Marmalaade!");
|
||||
|
||||
g_assert (test->peer != NULL);
|
||||
|
||||
/* Should have used our expected certificate */
|
||||
cert = g_tls_certificate_new_from_files (SRCDIR "/src/bridge/mock-client.crt",
|
||||
SRCDIR "/src/bridge/mock-client.key", &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_assert (g_tls_certificate_is_same (test->peer, cert));
|
||||
g_object_unref (cert);
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (channel), (gpointer *)&channel);
|
||||
g_object_unref (channel);
|
||||
g_assert (channel == NULL);
|
||||
}
|
||||
|
||||
static const gchar fixture_tls_authority_good[] =
|
||||
"{ \"authority\": { \"file\": \"" SRCDIR "/src/bridge/mock-server.crt\" } }";
|
||||
|
||||
static void
|
||||
test_tls_authority_good (TestTls *test,
|
||||
gconstpointer json)
|
||||
{
|
||||
gboolean closed = FALSE;
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
JsonObject *tls;
|
||||
GError *error = NULL;
|
||||
const gchar *control;
|
||||
GBytes *bytes;
|
||||
|
||||
tls = cockpit_json_parse_object (json, -1, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", test->port);
|
||||
json_object_set_string_member (options, "payload", "http-stream1");
|
||||
json_object_set_string_member (options, "method", "GET");
|
||||
json_object_set_string_member (options, "path", "/test");
|
||||
json_object_set_object_member (options, "tls", tls);
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_HTTP_STREAM,
|
||||
"transport", test->transport,
|
||||
"id", "444",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
|
||||
/* Tell HTTP we have no more data to send */
|
||||
control = "{\"command\": \"done\", \"channel\": \"444\"}";
|
||||
bytes = g_bytes_new_static (control, strlen (control));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (test->transport), NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_set_flag), &closed);
|
||||
|
||||
while (closed == FALSE)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
assert_channel_response (test->transport, "444",
|
||||
"{\"status\":200,\"reason\":\"OK\",\"headers\":{" STATIC_HEADERS "}}",
|
||||
"Oh Marmalaade!");
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (channel), (gpointer *)&channel);
|
||||
g_object_unref (channel);
|
||||
g_assert (channel == NULL);
|
||||
}
|
||||
|
||||
static const gchar fixture_tls_authority_bad[] =
|
||||
"{ \"certificate\": { \"file\": \"" SRCDIR "/src/bridge/mock-client.crt\" },"
|
||||
" \"key\": { \"file\": \"" SRCDIR "/src/bridge/mock-client.key\" }, "
|
||||
" \"authority\": { \"file\": \"" SRCDIR "/src/bridge/mock-client.crt\" } }";
|
||||
|
||||
static void
|
||||
test_tls_authority_bad (TestTls *test,
|
||||
gconstpointer json)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
JsonObject *tls;
|
||||
GError *error = NULL;
|
||||
const gchar *control;
|
||||
GBytes *bytes;
|
||||
JsonObject *resp;
|
||||
|
||||
tls = cockpit_json_parse_object (json, -1, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
options = json_object_new ();
|
||||
json_object_set_int_member (options, "port", test->port);
|
||||
json_object_set_string_member (options, "payload", "http-stream1");
|
||||
json_object_set_string_member (options, "method", "GET");
|
||||
json_object_set_string_member (options, "path", "/test");
|
||||
json_object_set_object_member (options, "tls", tls);
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_HTTP_STREAM,
|
||||
"transport", test->transport,
|
||||
"id", "444",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
cockpit_expect_log ("cockpit-bridge", G_LOG_LEVEL_MESSAGE,
|
||||
"*Unacceptable TLS certificate");
|
||||
|
||||
json_object_unref (options);
|
||||
|
||||
/* Tell HTTP we have no more data to send */
|
||||
control = "{\"command\": \"done\", \"channel\": \"444\"}";
|
||||
bytes = g_bytes_new_static (control, strlen (control));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (test->transport), NULL, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
|
||||
while (mock_transport_count_sent (test->transport) < 1)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
resp = mock_transport_pop_control (test->transport);
|
||||
cockpit_assert_json_eq (resp, "{\"command\":\"close\",\"channel\":\"444\",\"problem\":\"unknown-hostkey\"}");
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (channel), (gpointer *)&channel);
|
||||
g_object_unref (channel);
|
||||
g_assert (channel == NULL);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
char *ip = non_local_ip ();
|
||||
int result;
|
||||
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/http-stream/host-header", TestGeneral, "localhost",
|
||||
setup_general, test_host_header, teardown_general);
|
||||
g_test_add ("/http-stream/address-host-header", TestGeneral, ip,
|
||||
setup_general, test_host_header, teardown_general);
|
||||
|
||||
g_test_add ("/http-stream/http-stream2", TestGeneral, NULL,
|
||||
setup_general, test_http_stream2, teardown_general);
|
||||
g_test_add ("/http-stream/cannot-connect", TestGeneral, NULL,
|
||||
setup_general, test_cannot_connect, teardown_general);
|
||||
|
||||
g_test_add_func ("/http-stream/parse_keepalive", test_parse_keep_alive);
|
||||
g_test_add_func ("/http-stream/http_chunked", test_http_chunked);
|
||||
|
||||
g_test_add ("/http-stream/tls/basic", TestTls, NULL,
|
||||
setup_tls, test_tls_basic, teardown_tls);
|
||||
g_test_add ("/http-stream/tls/certificate-data", TestTls, fixture_tls_certificate_data,
|
||||
setup_tls, test_tls_certificate, teardown_tls);
|
||||
g_test_add ("/http-stream/tls/certificate-file", TestTls, fixture_tls_certificate_file,
|
||||
setup_tls, test_tls_certificate, teardown_tls);
|
||||
g_test_add ("/http-stream/tls/certificate-data-file", TestTls, fixture_tls_certificate_data_file,
|
||||
setup_tls, test_tls_certificate, teardown_tls);
|
||||
g_test_add ("/http-stream/tls/certificate-file-data", TestTls, fixture_tls_certificate_file_data,
|
||||
setup_tls, test_tls_certificate, teardown_tls);
|
||||
g_test_add ("/http-stream/tls/authority-good", TestTls, fixture_tls_authority_good,
|
||||
setup_tls, test_tls_authority_good, teardown_tls);
|
||||
g_test_add ("/http-stream/tls/authority-bad", TestTls, fixture_tls_authority_bad,
|
||||
setup_tls, test_tls_authority_bad, teardown_tls);
|
||||
|
||||
result = g_test_run ();
|
||||
g_free (ip);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1,998 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <math.h>
|
||||
|
||||
#include "cockpitmetrics.h"
|
||||
|
||||
#include "cockpitinternalmetrics.h"
|
||||
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct {
|
||||
MockTransport *transport;
|
||||
CockpitMetrics *channel;
|
||||
gchar *problem;
|
||||
gboolean channel_closed;
|
||||
} TestCase;
|
||||
|
||||
static void
|
||||
on_channel_close (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestCase *tc = user_data;
|
||||
g_assert (tc->channel_closed == FALSE);
|
||||
tc->problem = g_strdup (problem);
|
||||
tc->channel_closed = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_transport_closed (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
typedef struct _CockpitMetrics MockMetrics;
|
||||
typedef struct _CockpitMetricsClass MockMetricsClass;
|
||||
|
||||
GType mock_metrics_get_type (void);
|
||||
|
||||
G_DEFINE_TYPE (MockMetrics, mock_metrics, COCKPIT_TYPE_METRICS);
|
||||
|
||||
static void
|
||||
mock_metrics_init (MockMetrics *self)
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
|
||||
static void
|
||||
mock_metrics_class_init (MockMetricsClass *self)
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
|
||||
static void
|
||||
setup (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
tc->transport = mock_transport_new ();
|
||||
g_signal_connect (tc->transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
tc->channel = g_object_new (mock_metrics_get_type (),
|
||||
"transport", tc->transport,
|
||||
"id", "1234",
|
||||
NULL);
|
||||
g_signal_connect (tc->channel, "closed", G_CALLBACK (on_channel_close), tc);
|
||||
|
||||
/* Switch off compression by default. Compression is done by
|
||||
* comparing two floating point values for exact equality, and we
|
||||
* can't guarantee that we get the same behavior everywhere.
|
||||
*/
|
||||
cockpit_metrics_set_compress (tc->channel, FALSE);
|
||||
}
|
||||
|
||||
static GBytes *
|
||||
recv_bytes (MockTransport *transport)
|
||||
{
|
||||
GBytes *msg;
|
||||
while ((msg = mock_transport_pop_channel (transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
recv_object (MockTransport *transport)
|
||||
{
|
||||
GBytes *msg = recv_bytes (transport);
|
||||
JsonObject *res = cockpit_json_parse_bytes (msg, NULL);
|
||||
g_assert (res != NULL);
|
||||
return res;
|
||||
}
|
||||
|
||||
static JsonArray *
|
||||
recv_array (MockTransport *transport)
|
||||
{
|
||||
GBytes *msg;
|
||||
GError *error = NULL;
|
||||
JsonArray *array;
|
||||
JsonNode *node;
|
||||
|
||||
msg = recv_bytes (transport);
|
||||
node = cockpit_json_parse (g_bytes_get_data (msg, NULL), g_bytes_get_size (msg), &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (json_node_get_node_type (node), ==, JSON_NODE_ARRAY);
|
||||
|
||||
array = json_node_dup_array (node);
|
||||
json_node_free (node);
|
||||
return array;
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
cockpit_assert_expected ();
|
||||
|
||||
g_object_unref (tc->transport);
|
||||
|
||||
if (tc->channel)
|
||||
{
|
||||
g_object_add_weak_pointer (G_OBJECT (tc->channel), (gpointer *)&tc->channel);
|
||||
g_object_unref (tc->channel);
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
g_free (tc->problem);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_sample_msg (const char *domain,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *func,
|
||||
TestCase *tc,
|
||||
const gchar *json_str)
|
||||
{
|
||||
JsonArray *array = recv_array (tc->transport);
|
||||
_cockpit_assert_json_eq_msg (domain, file, line, func, array, json_str);
|
||||
json_array_unref (array);
|
||||
}
|
||||
|
||||
#define assert_sample(tc, json) \
|
||||
(assert_sample_msg (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (tc), (json)))
|
||||
|
||||
static JsonObject *
|
||||
json_obj_msg (const char *domain,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *func,
|
||||
const gchar *json_str)
|
||||
{
|
||||
GError *error = NULL;
|
||||
JsonObject *res = cockpit_json_parse_object (json_str, -1, &error);
|
||||
if (error)
|
||||
g_assertion_message_error (domain, file, line, func, "error", error, 0, 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
#define json_obj(json_str) \
|
||||
(json_obj_msg (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (json_str)))
|
||||
|
||||
static void
|
||||
send_sample (TestCase *tc, gint64 timestamp, int n, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start (ap, n);
|
||||
|
||||
double **buffer;
|
||||
buffer = cockpit_metrics_get_data_buffer (tc->channel);
|
||||
for (int i = 0; i < n; i++)
|
||||
buffer[i][0] = va_arg (ap, double);
|
||||
cockpit_metrics_send_data (tc->channel, timestamp);
|
||||
cockpit_metrics_flush_data (tc->channel);
|
||||
|
||||
va_end (ap);
|
||||
}
|
||||
|
||||
static void
|
||||
send_instance_sample (TestCase *tc, gint64 timestamp, int n, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start (ap, n);
|
||||
|
||||
double **buffer;
|
||||
buffer = cockpit_metrics_get_data_buffer (tc->channel);
|
||||
for (int i = 0; i < n; i++)
|
||||
buffer[0][i] = va_arg (ap, double);
|
||||
cockpit_metrics_send_data (tc->channel, timestamp);
|
||||
cockpit_metrics_flush_data (tc->channel);
|
||||
|
||||
va_end (ap);
|
||||
}
|
||||
|
||||
static void
|
||||
test_compression (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
cockpit_metrics_set_compress (tc->channel, TRUE);
|
||||
|
||||
JsonObject *meta = json_obj ("{ 'metrics': [ { 'name': 'foo' },"
|
||||
" { 'name': 'bar' }"
|
||||
" ],"
|
||||
" 'interval': 1000"
|
||||
"}");
|
||||
cockpit_metrics_send_meta (tc->channel, meta, FALSE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_sample (tc, 0, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[0,0]]");
|
||||
send_sample (tc, 1000, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[]]");
|
||||
send_sample (tc, 2000, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[]]");
|
||||
|
||||
send_sample (tc, 3000, 2, 0.0, 1.0);
|
||||
assert_sample (tc, "[[null, 1]]");
|
||||
|
||||
send_sample (tc, 4000, 2, 1.0, 1.0);
|
||||
assert_sample (tc, "[[1]]");
|
||||
|
||||
json_object_unref (meta);
|
||||
}
|
||||
|
||||
static void
|
||||
test_compression_reset (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
cockpit_metrics_set_compress (tc->channel, TRUE);
|
||||
|
||||
JsonObject *meta = json_obj ("{ 'metrics': [ { 'name': 'foo' },"
|
||||
" { 'name': 'bar' }"
|
||||
" ],"
|
||||
" 'interval': 1000"
|
||||
"}");
|
||||
cockpit_metrics_send_meta (tc->channel, meta, FALSE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_sample (tc, 0, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[0,0]]");
|
||||
send_sample (tc, 1000, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[]]");
|
||||
|
||||
cockpit_metrics_send_meta (tc->channel, meta, TRUE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_sample (tc, 2000, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[0,0]]");
|
||||
send_sample (tc, 3000, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[]]");
|
||||
|
||||
json_object_unref (meta);
|
||||
}
|
||||
|
||||
static void
|
||||
test_derive_delta (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *meta = json_obj ("{ 'metrics': [ { 'name': 'foo',"
|
||||
" 'derive': 'delta'"
|
||||
" }"
|
||||
" ],"
|
||||
" 'interval': 100"
|
||||
"}");
|
||||
cockpit_metrics_send_meta (tc->channel, meta, FALSE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_sample (tc, 0, 1, 0.0);
|
||||
assert_sample (tc, "[[false]]");
|
||||
send_sample (tc, 100, 1, 10.0);
|
||||
assert_sample (tc, "[[10]]");
|
||||
send_sample (tc, 200, 1, 20.0);
|
||||
assert_sample (tc, "[[10]]");
|
||||
send_sample (tc, 300, 1, 40.0);
|
||||
assert_sample (tc, "[[20]]");
|
||||
send_sample (tc, 400, 1, 30.0);
|
||||
assert_sample (tc, "[[-10]]");
|
||||
send_sample (tc, 500, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
send_sample (tc, 600, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
send_sample (tc, 700, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
|
||||
cockpit_metrics_send_meta (tc->channel, meta, TRUE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_sample (tc, 800, 1, 30.0);
|
||||
assert_sample (tc, "[[false]]");
|
||||
send_sample (tc, 900, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
send_sample (tc, 1000, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
send_sample (tc, 1100, 1, 40.0);
|
||||
assert_sample (tc, "[[10]]");
|
||||
send_sample (tc, 1200, 1, 40.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
|
||||
json_object_unref (meta);
|
||||
}
|
||||
|
||||
static void
|
||||
test_derive_rate_no_interpolate (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
cockpit_metrics_set_interpolate (tc->channel, FALSE);
|
||||
|
||||
JsonObject *meta = json_obj ("{ 'metrics': [ { 'name': 'foo',"
|
||||
" 'derive': 'rate'"
|
||||
" }"
|
||||
" ],"
|
||||
" 'interval': 100"
|
||||
"}");
|
||||
cockpit_metrics_send_meta (tc->channel, meta, FALSE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_sample (tc, 0, 1, 0.0);
|
||||
assert_sample (tc, "[[false]]");
|
||||
send_sample (tc, 100, 1, 10.0);
|
||||
assert_sample (tc, "[[100]]");
|
||||
send_sample (tc, 200, 1, 20.0);
|
||||
assert_sample (tc, "[[100]]");
|
||||
send_sample (tc, 300, 1, 40.0);
|
||||
assert_sample (tc, "[[200]]");
|
||||
send_sample (tc, 400, 1, 30.0);
|
||||
assert_sample (tc, "[[-100]]");
|
||||
send_sample (tc, 500, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
send_sample (tc, 600, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
send_sample (tc, 700, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
|
||||
cockpit_metrics_send_meta (tc->channel, meta, TRUE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_sample (tc, 800, 1, 30.0);
|
||||
assert_sample (tc, "[[false]]");
|
||||
send_sample (tc, 900, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
send_sample (tc, 1000, 1, 30.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
send_sample (tc, 1200, 1, 40.0); // double interval -> half rate
|
||||
assert_sample (tc, "[[50]]");
|
||||
send_sample (tc, 1200, 1, 40.0);
|
||||
assert_sample (tc, "[[false]]"); // divide by zero -> NaN -> false
|
||||
send_sample (tc, 1300, 1, 40.0);
|
||||
assert_sample (tc, "[[0]]");
|
||||
|
||||
json_object_unref (meta);
|
||||
}
|
||||
|
||||
/* Very specific functions to be used by test_interpolate for
|
||||
approximate sample assertions. (The only reason why we don't do
|
||||
this for all tests is that it is not fun to generalize this...)
|
||||
*/
|
||||
|
||||
static gboolean
|
||||
approx_equal (double a, double b)
|
||||
{
|
||||
return a == b || (fabs(a-b)/fmax(a, b) < 0.0001);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_2_approx_samples_msg (const char *domain,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *func,
|
||||
TestCase *tc,
|
||||
double val1,
|
||||
double val2)
|
||||
{
|
||||
JsonArray *array = recv_array (tc->transport);
|
||||
JsonArray *sub_array;
|
||||
|
||||
if (json_array_get_length (array) != 1)
|
||||
goto fail;
|
||||
sub_array = json_array_get_array_element (array, 0);
|
||||
if (json_array_get_length (sub_array) != 2)
|
||||
goto fail;
|
||||
if (!approx_equal (json_array_get_double_element (sub_array, 0), val1))
|
||||
goto fail;
|
||||
if (!approx_equal (json_array_get_double_element (sub_array, 1), val2))
|
||||
goto fail;
|
||||
|
||||
goto out;
|
||||
|
||||
fail:
|
||||
{
|
||||
JsonNode *node;
|
||||
gchar *escaped;
|
||||
gchar *msg;
|
||||
|
||||
node = json_node_new (JSON_NODE_ARRAY);
|
||||
json_node_set_array (node, array);
|
||||
escaped = cockpit_json_write (node, NULL);
|
||||
msg = g_strdup_printf ("%s does not approximately match [[%g,%g]]", escaped, val1, val2);
|
||||
g_assertion_message (domain, file, line, func, msg);
|
||||
g_free (msg);
|
||||
g_free (escaped);
|
||||
json_node_free (node);
|
||||
}
|
||||
|
||||
out:
|
||||
json_array_unref (array);
|
||||
}
|
||||
|
||||
#define assert_2_approx_samples(tc, val1, val2) \
|
||||
(assert_2_approx_samples_msg (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (tc), (val1), (val2)))
|
||||
|
||||
static void
|
||||
test_interpolate (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *meta = json_obj ("{ 'metrics': [ { 'name': 'foo'"
|
||||
" },"
|
||||
" {"
|
||||
" 'name': 'bar',"
|
||||
" 'derive': 'rate'"
|
||||
" }"
|
||||
" ],"
|
||||
" 'interval': 100"
|
||||
"}");
|
||||
cockpit_metrics_send_meta (tc->channel, meta, FALSE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
// rising by 10 for every 100 ms, with non-equally spaced samples
|
||||
|
||||
send_sample (tc, 0, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[0,false]]");
|
||||
send_sample (tc, 100, 2, 10.0, 10.0);
|
||||
assert_2_approx_samples (tc, 10, 100);
|
||||
send_sample (tc, 250, 2, 25.0, 25.0);
|
||||
assert_2_approx_samples (tc, 20, 100);
|
||||
send_sample (tc, 300, 2, 30.0, 30.0);
|
||||
assert_2_approx_samples (tc, 30, 100);
|
||||
send_sample (tc, 500, 2, 50.0, 50.0);
|
||||
assert_2_approx_samples (tc, 40, 100);
|
||||
send_sample (tc, 500, 2, 50.0, 50.0);
|
||||
assert_2_approx_samples (tc, 50, 100);
|
||||
|
||||
json_object_unref (meta);
|
||||
}
|
||||
|
||||
static void
|
||||
test_instances (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *meta = json_obj ("{ 'metrics': [ { 'name': 'foo',"
|
||||
" 'instances': [ 'a', 'b' ]"
|
||||
" }"
|
||||
" ],"
|
||||
" 'interval': 1000"
|
||||
"}");
|
||||
cockpit_metrics_send_meta (tc->channel, meta, FALSE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_instance_sample (tc, 0, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[[0,0]]]");
|
||||
send_instance_sample (tc, 1000, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[[0,0]]]");
|
||||
send_instance_sample (tc, 2000, 2, 0.0, 0.0);
|
||||
assert_sample (tc, "[[[0,0]]]");
|
||||
|
||||
send_instance_sample (tc, 3000, 2, 0.0, 1.0);
|
||||
assert_sample (tc, "[[[0, 1]]]");
|
||||
|
||||
send_instance_sample (tc, 4000, 2, 1.0, 1.0);
|
||||
assert_sample (tc, "[[[1, 1]]]");
|
||||
|
||||
json_object_unref (meta);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dynamic_instances (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *meta = json_obj ("{ 'metrics': [ { 'name': 'foo',"
|
||||
" 'instances': [ 'a' ],"
|
||||
" 'derive': 'delta'"
|
||||
" }"
|
||||
" ],"
|
||||
" 'interval': 100"
|
||||
"}");
|
||||
cockpit_metrics_send_meta (tc->channel, meta, FALSE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
send_instance_sample (tc, 0, 1, 0.0);
|
||||
assert_sample (tc, "[[[false]]]");
|
||||
send_instance_sample (tc, 100, 1, 10.0);
|
||||
assert_sample (tc, "[[[10]]]");
|
||||
send_instance_sample (tc, 200, 1, 20.0);
|
||||
assert_sample (tc, "[[[10]]]");
|
||||
|
||||
json_object_unref (meta);
|
||||
meta = json_obj ("{ 'metrics': [ { 'name': 'foo',"
|
||||
" 'instances': [ 'b', 'a' ],"
|
||||
" 'derive': 'delta'"
|
||||
" }"
|
||||
" ],"
|
||||
" 'interval': 100"
|
||||
"}");
|
||||
cockpit_metrics_send_meta (tc->channel, meta, FALSE);
|
||||
json_object_unref (recv_object (tc->transport));
|
||||
|
||||
/* Instance 'a' is now at a different index. The 'delta' derivation
|
||||
should continue to work, but no compression should happen.
|
||||
*/
|
||||
|
||||
send_instance_sample (tc, 300, 2, 0.0, 30.0);
|
||||
assert_sample (tc, "[[[false,10]]]");
|
||||
send_instance_sample (tc, 400, 2, 10.0, 20.0);
|
||||
assert_sample (tc, "[[[10,-10]]]");
|
||||
send_instance_sample (tc, 500, 2, 10.0, 40.0);
|
||||
assert_sample (tc, "[[[0,20]]]");
|
||||
send_instance_sample (tc, 600, 2, 10.0, 50.0);
|
||||
assert_sample (tc, "[[[0,10]]]");
|
||||
send_instance_sample (tc, 700, 2, 10.0, 60.0);
|
||||
assert_sample (tc, "[[[0,10]]]");
|
||||
|
||||
json_object_unref (meta);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_not_root_mount (JsonArray *array,
|
||||
guint index_,
|
||||
JsonNode *element_node,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_assert_cmpstr (json_node_get_string (element_node), !=, "/");
|
||||
}
|
||||
|
||||
static void
|
||||
test_omit_instances (void)
|
||||
{
|
||||
MockTransport *transport = mock_transport_new ();
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options = json_obj ("{ 'metrics': [ { 'name': 'mount.total' } ],"
|
||||
" 'omit-instances': [ '/' ],"
|
||||
" 'interval': 1000"
|
||||
"}");
|
||||
GBytes *msg;
|
||||
JsonObject *res, *mount_total;
|
||||
JsonArray *metrics;
|
||||
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
|
||||
channel = g_object_new (cockpit_internal_metrics_get_type (),
|
||||
"transport", transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
cockpit_channel_prepare (channel);
|
||||
|
||||
/* receive meta information */
|
||||
while ((msg = mock_transport_pop_channel (transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
res = cockpit_json_parse_bytes (msg, NULL);
|
||||
g_assert (res != NULL);
|
||||
g_assert (json_object_has_member (res, "metrics"));
|
||||
|
||||
/* metrics should have the form [{"name":"mount.total","instances":["/boot",...]}] */
|
||||
metrics = json_object_get_array_member (res, "metrics");
|
||||
g_assert_cmpint (json_array_get_length (metrics), ==, 1);
|
||||
mount_total = json_array_get_object_element (metrics, 0);
|
||||
g_assert (mount_total);
|
||||
g_assert_cmpstr (json_object_get_string_member (mount_total, "name"), ==, "mount.total");
|
||||
|
||||
/* instances should not contain the omitted "/" */
|
||||
json_array_foreach_element (json_object_get_array_member (mount_total, "instances"),
|
||||
assert_not_root_mount,
|
||||
NULL);
|
||||
|
||||
json_object_unref (res);
|
||||
g_object_unref (channel);
|
||||
json_object_unref (options);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
on_close_get_problem (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar **result = user_data;
|
||||
g_assert (result != NULL);
|
||||
g_assert (*result == NULL);
|
||||
*result = g_strdup (problem ? problem : "");
|
||||
}
|
||||
|
||||
static void
|
||||
test_not_supported (void)
|
||||
{
|
||||
MockTransport *transport;
|
||||
CockpitMetrics *channel;
|
||||
gchar *problem = NULL;
|
||||
JsonObject *options;
|
||||
|
||||
cockpit_expect_message ("*unknown internal metric*");
|
||||
|
||||
transport = mock_transport_new ();
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
options = json_obj ("{ 'metrics': [ { 'name': 'invalid.metrics',"
|
||||
" 'instances': [ 'b', 'a' ],"
|
||||
" 'derive': 'delta'"
|
||||
" }"
|
||||
" ],"
|
||||
" 'interval': 100"
|
||||
"}");
|
||||
channel = g_object_new (cockpit_internal_metrics_get_type (),
|
||||
"transport", transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
json_object_unref (options);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_close_get_problem), &problem);
|
||||
|
||||
while (problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpstr (problem, ==, "not-supported");
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (channel), (gpointer *)&channel);
|
||||
g_object_unref (channel);
|
||||
g_assert (channel == NULL);
|
||||
|
||||
g_object_unref (transport);
|
||||
g_free (problem);
|
||||
}
|
||||
|
||||
static void
|
||||
test_deprecated_net_all (void)
|
||||
{
|
||||
MockTransport *transport = mock_transport_new ();
|
||||
CockpitChannel *channel;
|
||||
/* network.all.* is not being used any more in current cockpit, but in older
|
||||
* Dashboards; ensure it keeps working */
|
||||
JsonObject *options = json_obj ("{ 'metrics': [ { 'name': 'network.all.tx' }, { 'name': 'network.all.rx' } ],"
|
||||
" 'interval': 100"
|
||||
"}");
|
||||
GBytes *msg;
|
||||
JsonObject *res, *metric;
|
||||
JsonNode *node;
|
||||
JsonArray *metrics, *values;
|
||||
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
|
||||
channel = g_object_new (cockpit_internal_metrics_get_type (),
|
||||
"transport", transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
cockpit_metrics_set_compress (COCKPIT_METRICS (channel), FALSE);
|
||||
cockpit_channel_prepare (channel);
|
||||
|
||||
/* receive meta information */
|
||||
while ((msg = mock_transport_pop_channel (transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
res = cockpit_json_parse_bytes (msg, NULL);
|
||||
g_assert (res != NULL);
|
||||
g_assert (json_object_has_member (res, "metrics"));
|
||||
|
||||
/* metrics should have the form [{"name":"network.all.tx","units":"bytes","semantics":"counter"}, ...] */
|
||||
metrics = json_object_get_array_member (res, "metrics");
|
||||
g_assert_cmpint (json_array_get_length (metrics), ==, 2);
|
||||
|
||||
metric = json_array_get_object_element (metrics, 0);
|
||||
g_assert (metric);
|
||||
g_assert_cmpstr (json_object_get_string_member (metric, "name"), ==, "network.all.tx");
|
||||
g_assert_cmpstr (json_object_get_string_member (metric, "units"), ==, "bytes");
|
||||
g_assert (!json_object_has_member (metric, "instances"));
|
||||
|
||||
metric = json_array_get_object_element (metrics, 1);
|
||||
g_assert (metric);
|
||||
g_assert_cmpstr (json_object_get_string_member (metric, "name"), ==, "network.all.rx");
|
||||
g_assert_cmpstr (json_object_get_string_member (metric, "units"), ==, "bytes");
|
||||
g_assert (!json_object_has_member (metric, "instances"));
|
||||
|
||||
json_object_unref (res);
|
||||
|
||||
/* receive data; should have the form [[123,456]] */
|
||||
while ((msg = mock_transport_pop_channel (transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
node = cockpit_json_parse (g_bytes_get_data (msg, NULL), g_bytes_get_size (msg), NULL);
|
||||
g_assert (node);
|
||||
metrics = json_node_get_array (node);
|
||||
g_assert (metrics != NULL);
|
||||
g_assert_cmpint (json_array_get_length (metrics), ==, 1);
|
||||
values = json_array_get_array_element (metrics, 0);
|
||||
g_assert_cmpint (json_array_get_length (values), ==, 2);
|
||||
g_assert_cmpint (json_array_get_int_element (values, 0), >=, 0);
|
||||
g_assert_cmpint (json_array_get_int_element (values, 1), >=, 0);
|
||||
|
||||
json_node_free (node);
|
||||
|
||||
g_object_unref (channel);
|
||||
json_object_unref (options);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_cgroup (void)
|
||||
{
|
||||
MockTransport *transport = mock_transport_new ();
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options = json_obj ("{ 'metrics': [ { 'name': 'cgroup.memory.usage' }, "
|
||||
" { 'name': 'cgroup.memory.limit' }, "
|
||||
" { 'name': 'cgroup.memory.sw-usage' }, "
|
||||
" { 'name': 'cgroup.memory.sw-limit' }, "
|
||||
" { 'name': 'cgroup.cpu.usage' }, "
|
||||
" { 'name': 'cgroup.cpu.shares' } ], "
|
||||
" 'interval': 1000"
|
||||
"}");
|
||||
GBytes *msg;
|
||||
JsonObject *res, *description;
|
||||
JsonArray *metrics, *instances;
|
||||
JsonArray *samples;
|
||||
GError *error = NULL;
|
||||
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
|
||||
channel = g_object_new (cockpit_internal_metrics_get_type (),
|
||||
"transport", transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
cockpit_channel_prepare (channel);
|
||||
|
||||
/* receive meta information */
|
||||
while ((msg = mock_transport_pop_channel (transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
res = cockpit_json_parse_bytes (msg, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (res != NULL);
|
||||
g_assert (json_object_has_member (res, "metrics"));
|
||||
|
||||
/* metrics should have the form [{"name":"...","instances":["name1", ...]}] */
|
||||
metrics = json_object_get_array_member (res, "metrics");
|
||||
g_assert_cmpint (json_array_get_length (metrics), ==, 6);
|
||||
description = json_array_get_object_element (metrics, 0);
|
||||
g_assert (description);
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "name"), ==, "cgroup.memory.usage");
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "units"), ==, "bytes");
|
||||
|
||||
/* we can't assert any contents about instances and samples, as build environments may not even have
|
||||
* any cgroup controller; just make sure that the data structure is as expected */
|
||||
instances = json_object_get_array_member (description, "instances");
|
||||
g_assert_cmpint (json_array_get_length (instances), >=, 0);
|
||||
|
||||
/* next message should have some actual values; looks like [[...],[...],...]]] */
|
||||
samples = recv_array (transport);
|
||||
g_assert_cmpint (json_array_get_length (samples), ==, 1);
|
||||
g_assert_cmpint (json_array_get_length (json_array_get_array_element (samples, 0)), ==, 6);
|
||||
|
||||
json_array_unref (samples);
|
||||
|
||||
json_object_unref (res);
|
||||
g_object_unref (channel);
|
||||
json_object_unref (options);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_cpu_cores (void)
|
||||
{
|
||||
MockTransport *transport = mock_transport_new ();
|
||||
CockpitChannel *channel;
|
||||
|
||||
JsonObject *options = json_obj ("{ 'metrics': [ { 'name': 'cpu.core.nice', 'derive': 'rate' }, "
|
||||
" { 'name': 'cpu.core.user', 'derive': 'rate' }, "
|
||||
" { 'name': 'cpu.core.system', 'derive': 'rate' }, "
|
||||
" { 'name': 'cpu.core.iowait', 'derive': 'rate' } ], "
|
||||
" 'interval': 1000"
|
||||
"}");
|
||||
GBytes *msg;
|
||||
JsonObject *res, *description;
|
||||
JsonArray *metrics, *instances, *all, *nice;
|
||||
JsonArray *samples;
|
||||
GError *error = NULL;
|
||||
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
|
||||
channel = g_object_new (cockpit_internal_metrics_get_type (),
|
||||
"transport", transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
cockpit_channel_prepare (channel);
|
||||
|
||||
/* receive meta information */
|
||||
while ((msg = mock_transport_pop_channel (transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
res = cockpit_json_parse_bytes (msg, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (res != NULL);
|
||||
g_assert (json_object_has_member (res, "metrics"));
|
||||
|
||||
/* metrics should have the form [{"name":"...","instances":[[1, 0, ...], ...]}] */
|
||||
metrics = json_object_get_array_member (res, "metrics");
|
||||
g_assert_cmpint (json_array_get_length (metrics), ==, 4);
|
||||
description = json_array_get_object_element (metrics, 0);
|
||||
g_assert (description);
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "name"), ==, "cpu.core.nice");
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "units"), ==, "millisec");
|
||||
|
||||
/* Array contains value for each core */
|
||||
instances = json_object_get_array_member (description, "instances");
|
||||
g_assert_cmpint (json_array_get_length (instances), ==, sysconf(_SC_NPROCESSORS_ONLN));
|
||||
|
||||
/* Value of each core is somewhere between 0 and 1000 */
|
||||
samples = recv_array (transport);
|
||||
g_assert_cmpint (json_array_get_length (samples), ==, 1);
|
||||
all = json_array_get_array_element (samples, 0);
|
||||
g_assert_cmpint (json_array_get_length (all), ==, 4);
|
||||
nice = json_array_get_array_element (all, 0);
|
||||
g_assert_cmpint (json_array_get_length (nice), ==, sysconf(_SC_NPROCESSORS_ONLN));
|
||||
g_assert_cmpint (json_array_get_int_element (nice, 0), >=, 0);
|
||||
g_assert_cmpint (json_array_get_int_element (nice, 0), <=, 1000);
|
||||
|
||||
json_array_unref (samples);
|
||||
|
||||
json_object_unref (res);
|
||||
g_object_unref (channel);
|
||||
json_object_unref (options);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_cpu_temperature (void)
|
||||
{
|
||||
MockTransport *transport = mock_transport_new ();
|
||||
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
|
||||
g_autoptr(JsonObject) options = json_obj ("{ 'metrics': [ { 'name': 'cpu.temperature' } ],"
|
||||
" 'interval': 1000"
|
||||
"}");
|
||||
|
||||
CockpitChannel *channel = g_object_new (cockpit_internal_metrics_get_type (),
|
||||
"transport", transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
cockpit_channel_prepare (channel);
|
||||
|
||||
g_autoptr(GBytes) msg = NULL;
|
||||
/* receive meta information */
|
||||
while ((msg = mock_transport_pop_channel (transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(JsonObject) res = cockpit_json_parse_bytes (msg, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (res != NULL);
|
||||
g_assert (json_object_has_member (res, "metrics"));
|
||||
|
||||
JsonArray *metrics = json_object_get_array_member (res, "metrics");
|
||||
g_assert_cmpint (json_array_get_length (metrics), ==, 1);
|
||||
|
||||
JsonObject *description = json_array_get_object_element (metrics, 0);
|
||||
g_assert (description);
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "name"), ==, "cpu.temperature");
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "units"), ==, "celsius");
|
||||
|
||||
g_autoptr(JsonArray) samples = recv_array (transport);
|
||||
g_assert_cmpint (json_array_get_length (samples), ==, 1);
|
||||
JsonArray *core = json_array_get_array_element (samples, 0);
|
||||
g_assert_cmpint (json_array_get_length (core), ==, 1);
|
||||
JsonArray *temperature = json_array_get_array_element (core, 0);
|
||||
|
||||
// file does not exist in virtual machines, skip value check
|
||||
if (json_array_get_length (temperature) >= 1)
|
||||
{
|
||||
g_assert_cmpint (json_array_get_int_element (temperature, 0), >, 0);
|
||||
g_assert_cmpint (json_array_get_int_element (temperature, 0), <, 150);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_cgroup_disk_io (void)
|
||||
{
|
||||
MockTransport *transport = mock_transport_new ();
|
||||
|
||||
g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
|
||||
g_autoptr(JsonObject) options = json_obj ("{ 'metrics': [ { 'name': 'disk.cgroup.read' }, { 'name': 'disk.cgroup.written' } ],"
|
||||
" 'interval': 1000"
|
||||
"}");
|
||||
|
||||
CockpitChannel *channel = g_object_new (cockpit_internal_metrics_get_type (),
|
||||
"transport", transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
cockpit_channel_prepare (channel);
|
||||
|
||||
g_autoptr(GBytes) msg = NULL;
|
||||
/* receive meta information */
|
||||
while ((msg = mock_transport_pop_channel (transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(JsonObject) res = cockpit_json_parse_bytes (msg, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (res != NULL);
|
||||
g_assert (json_object_has_member (res, "metrics"));
|
||||
|
||||
JsonArray *metrics = json_object_get_array_member (res, "metrics");
|
||||
g_assert_cmpint (json_array_get_length (metrics), ==, 2);
|
||||
|
||||
JsonObject *description = json_array_get_object_element (metrics, 0);
|
||||
g_assert (description);
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "name"), ==, "disk.cgroup.read");
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "units"), ==, "bytes");
|
||||
|
||||
description = json_array_get_object_element (metrics, 1);
|
||||
g_assert (description);
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "name"), ==, "disk.cgroup.written");
|
||||
g_assert_cmpstr (json_object_get_string_member (description, "units"), ==, "bytes");
|
||||
|
||||
g_autoptr(JsonArray) samples = recv_array (transport);
|
||||
g_assert_cmpint (json_array_get_length (samples), ==, 1);
|
||||
JsonArray *core = json_array_get_array_element (samples, 0);
|
||||
g_assert_cmpint (json_array_get_length (core), ==, 2);
|
||||
JsonArray *read = json_array_get_array_element (core, 0);
|
||||
JsonArray *write = json_array_get_array_element (core, 1);
|
||||
guint length = json_array_get_length (read);
|
||||
g_assert_cmpint (length, ==, json_array_get_length (write));
|
||||
g_assert_cmpint (length, >, 0);
|
||||
for (guint i = 0; i < length; i++)
|
||||
{
|
||||
g_assert_cmpint (json_array_get_int_element (read, i), >=, 0);
|
||||
g_assert_cmpint (json_array_get_int_element (write, i), >=, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/metrics/compression", TestCase, NULL,
|
||||
setup, test_compression, teardown);
|
||||
g_test_add ("/metrics/compression-reset", TestCase, NULL,
|
||||
setup, test_compression_reset, teardown);
|
||||
g_test_add ("/metrics/derive-delta", TestCase, NULL,
|
||||
setup, test_derive_delta, teardown);
|
||||
g_test_add ("/metrics/derive-rate", TestCase, NULL,
|
||||
setup, test_derive_rate_no_interpolate, teardown);
|
||||
g_test_add ("/metrics/interpolate", TestCase, NULL,
|
||||
setup, test_interpolate, teardown);
|
||||
|
||||
g_test_add ("/metrics/instances", TestCase, NULL,
|
||||
setup, test_instances, teardown);
|
||||
g_test_add ("/metrics/dynamic-instances", TestCase, NULL,
|
||||
setup, test_dynamic_instances, teardown);
|
||||
g_test_add_func ("/metrics/omit-instances", test_omit_instances);
|
||||
|
||||
g_test_add_func ("/metrics/not-supported", test_not_supported);
|
||||
|
||||
g_test_add_func ("/metrics/deprecated-net-all", test_deprecated_net_all);
|
||||
g_test_add_func ("/metrics/cgroup-memory", test_cgroup);
|
||||
|
||||
g_test_add_func ("/metrics/cpu-cores", test_cpu_cores);
|
||||
g_test_add_func ("/metrics/cpu-temperature", test_cpu_temperature);
|
||||
|
||||
g_test_add_func ("/metrics/cgroup-disk-io", test_cgroup_disk_io);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,645 +0,0 @@
|
|||
|
||||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitpacketchannel.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
#include <glib/gstdio.h>
|
||||
#include <gio/gunixsocketaddress.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Test
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
GSocket *listen_sock;
|
||||
GSource *listen_source;
|
||||
guint listen_tag;
|
||||
GSocket *conn_sock;
|
||||
GSource *conn_source;
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *channel_problem;
|
||||
gchar *unix_path;
|
||||
gchar *temp_file;
|
||||
} TestCase;
|
||||
|
||||
typedef struct {
|
||||
const gchar *payload;
|
||||
gboolean delay_listen;
|
||||
} Fixture;
|
||||
|
||||
static gboolean
|
||||
on_socket_input (GSocket *socket,
|
||||
GIOCondition cond,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar buffer[128 * 1024];
|
||||
GError *error = NULL;
|
||||
gssize ret, wret;
|
||||
|
||||
ret = g_socket_receive (socket, buffer, sizeof (buffer), NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
g_socket_shutdown (socket, FALSE, TRUE, &error);
|
||||
g_assert_no_error (error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_assert (ret > 0);
|
||||
wret = g_socket_send (socket, buffer, ret, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (wret == ret);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_socket_connection (GSocket *socket,
|
||||
GIOCondition cond,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestCase *tc = user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (tc->conn_source == NULL);
|
||||
tc->conn_sock = g_socket_accept (tc->listen_sock, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
tc->conn_source = g_socket_create_source (tc->conn_sock, G_IO_IN, NULL);
|
||||
g_source_set_callback (tc->conn_source, (GSourceFunc)on_socket_input, tc, NULL);
|
||||
g_source_attach (tc->conn_source, NULL);
|
||||
|
||||
/* Only one connection */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
setup (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
const Fixture *fixture = data;
|
||||
GSocketAddress *address;
|
||||
GError *error = NULL;
|
||||
|
||||
tc->temp_file = g_strdup ("/tmp/cockpit-test-XXXXXX");
|
||||
g_assert (close (g_mkstemp (tc->temp_file)) == 0);
|
||||
tc->unix_path = g_strconcat (tc->temp_file, ".sock", NULL);
|
||||
|
||||
address = g_unix_socket_address_new (tc->unix_path);
|
||||
|
||||
tc->listen_sock = g_socket_new (G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_SEQPACKET,
|
||||
G_SOCKET_PROTOCOL_DEFAULT, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_socket_bind (tc->listen_sock, address, TRUE, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_socket_listen (tc->listen_sock, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
tc->listen_source = g_socket_create_source (tc->listen_sock, G_IO_IN, NULL);
|
||||
g_source_set_callback (tc->listen_source, (GSourceFunc)on_socket_connection, tc, NULL);
|
||||
|
||||
if (!fixture || !fixture->delay_listen)
|
||||
g_source_attach (tc->listen_source, NULL);
|
||||
|
||||
g_object_unref (address);
|
||||
|
||||
tc->transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_closed_get_problem (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar **retval = user_data;
|
||||
g_assert (retval != NULL && *retval == NULL);
|
||||
*retval = g_strdup (problem ? problem : "");
|
||||
}
|
||||
|
||||
static void
|
||||
setup_channel (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
setup (tc, data);
|
||||
|
||||
tc->channel = cockpit_packet_channel_open (COCKPIT_TRANSPORT (tc->transport), "548", tc->unix_path);
|
||||
g_signal_connect (tc->channel, "closed", G_CALLBACK (on_closed_get_problem), &tc->channel_problem);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
if (tc->conn_source)
|
||||
{
|
||||
g_source_destroy (tc->conn_source);
|
||||
g_source_unref (tc->conn_source);
|
||||
}
|
||||
if (tc->listen_source)
|
||||
{
|
||||
g_source_destroy (tc->listen_source);
|
||||
g_source_unref (tc->listen_source);
|
||||
}
|
||||
g_clear_object (&tc->listen_sock);
|
||||
g_clear_object (&tc->conn_sock);
|
||||
|
||||
g_unlink (tc->unix_path);
|
||||
g_free (tc->unix_path);
|
||||
g_unlink (tc->temp_file);
|
||||
g_free (tc->temp_file);
|
||||
|
||||
g_object_unref (tc->transport);
|
||||
|
||||
if (tc->channel)
|
||||
{
|
||||
g_object_add_weak_pointer (G_OBJECT (tc->channel), (gpointer *)&tc->channel);
|
||||
g_object_unref (tc->channel);
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
g_free (tc->channel_problem);
|
||||
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
static void
|
||||
expect_control_message (JsonObject *options,
|
||||
const gchar *command,
|
||||
const gchar *expected_channel,
|
||||
...) G_GNUC_NULL_TERMINATED;
|
||||
|
||||
static void
|
||||
expect_control_message (JsonObject *options,
|
||||
const gchar *expected_command,
|
||||
const gchar *expected_channel,
|
||||
...)
|
||||
{
|
||||
const gchar *expect_option;
|
||||
const gchar *expect_value;
|
||||
const gchar *value;
|
||||
JsonNode *node;
|
||||
va_list va;
|
||||
|
||||
g_assert (options != NULL);
|
||||
g_assert_cmpstr (json_object_get_string_member (options, "command"), ==, expected_command);
|
||||
g_assert_cmpstr (json_object_get_string_member (options, "channel"), ==, expected_channel);
|
||||
|
||||
va_start (va, expected_channel);
|
||||
for (;;) {
|
||||
expect_option = va_arg (va, const gchar *);
|
||||
if (!expect_option)
|
||||
break;
|
||||
expect_value = va_arg (va, const gchar *);
|
||||
|
||||
value = NULL;
|
||||
node = json_object_get_member (options, expect_option);
|
||||
if (node && JSON_NODE_HOLDS_VALUE (node) && json_node_get_value_type (node) == G_TYPE_STRING)
|
||||
value = json_node_get_string (node);
|
||||
|
||||
g_assert_cmpstr (value, ==, expect_value);
|
||||
}
|
||||
va_end (va);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_echo (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *payload;
|
||||
GBytes *sent;
|
||||
|
||||
payload = g_bytes_new ("Marmalaade!", 11);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", payload);
|
||||
g_bytes_unref (payload);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
cockpit_assert_bytes_eq (sent, "Marmalaade!", 11);
|
||||
}
|
||||
|
||||
static void
|
||||
test_large (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *object;
|
||||
GBytes *sent;
|
||||
GBytes *options;
|
||||
GBytes *a, *b, *c;
|
||||
gchar *big;
|
||||
gchar *too_big;
|
||||
gchar *maximum;
|
||||
|
||||
/* Send something big: Should make it through */
|
||||
big = g_strnfill (32 * 1024, 'a');
|
||||
a = g_bytes_new_take (big, strlen (big));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", a);
|
||||
|
||||
/* Send something too big: Should be truncated */
|
||||
too_big = g_strnfill (80 * 1024, 'b');
|
||||
b = g_bytes_new_take (too_big, strlen (too_big));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", b);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 3)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/* Set the max-size to massive */
|
||||
object = cockpit_transport_build_json ("channel", "548", "command", "options", NULL);
|
||||
json_object_set_int_member (object, "max-size", 128 * 1024);
|
||||
options = cockpit_json_write_bytes (object);
|
||||
json_object_unref (object);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), NULL, options);
|
||||
|
||||
/* Send something too big again: This time not truncated */
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", b);
|
||||
|
||||
/* Lastly send the full maximum */
|
||||
maximum = g_strnfill (128 * 1024, 'c');
|
||||
c = g_bytes_new_take (maximum, strlen (maximum));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", c);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 5)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
cockpit_assert_bytes_eq (sent, big, strlen (big));
|
||||
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
cockpit_assert_bytes_eq (sent, too_big, 64 * 1024); /* Truncated */
|
||||
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
cockpit_assert_bytes_eq (sent, too_big, strlen (too_big));
|
||||
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
cockpit_assert_bytes_eq (sent, maximum, strlen (maximum));
|
||||
|
||||
g_bytes_unref (a);
|
||||
g_bytes_unref (b);
|
||||
g_bytes_unref (c);
|
||||
g_bytes_unref (options);
|
||||
}
|
||||
|
||||
static const Fixture fixture_connect_in_progress = { .delay_listen = TRUE };
|
||||
|
||||
static gboolean
|
||||
on_idle_listen (gpointer data)
|
||||
{
|
||||
TestCase *tc = data;
|
||||
g_source_attach (tc->listen_source, NULL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_connect_in_progress (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *payload;
|
||||
GBytes *sent;
|
||||
|
||||
payload = g_bytes_new ("Marmalaade!", 11);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", payload);
|
||||
g_bytes_unref (payload);
|
||||
|
||||
/* Attach listen source when idle */
|
||||
g_idle_add (on_idle_listen, tc);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
cockpit_assert_bytes_eq (sent, "Marmalaade!", 11);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_shutdown (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *payload;
|
||||
GError *error = NULL;
|
||||
JsonObject *sent;
|
||||
|
||||
payload = cockpit_transport_build_control ("channel", "548", "command", "done", NULL);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), NULL, payload);
|
||||
g_bytes_unref (payload);
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/*
|
||||
* Close down the write end of the socket (what
|
||||
* CockpitPacketChannel is reading from)
|
||||
*/
|
||||
g_socket_shutdown (tc->conn_sock, FALSE, TRUE, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
while (tc->channel_problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpstr (tc->channel_problem, ==, "");
|
||||
sent = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (sent, "ready", "548", NULL);
|
||||
sent = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (sent, "done", "548", NULL);
|
||||
|
||||
sent = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (sent, "close", "548", "problem", NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_close_normal (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *payload;
|
||||
GBytes *sent;
|
||||
JsonObject *control;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
payload = g_bytes_new ("Marmalaade!", 11);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", payload);
|
||||
cockpit_channel_close (tc->channel, NULL);
|
||||
g_bytes_unref (payload);
|
||||
|
||||
/* Wait until channel closes */
|
||||
while (tc->channel_problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/* Shouldn't have had a chance to send message */
|
||||
g_assert_cmpstr (tc->channel_problem, ==, "");
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
g_assert (sent == NULL);
|
||||
|
||||
control = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (control, "ready", "548", NULL);
|
||||
|
||||
control = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (control, "close", "548", "problem", NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_close_problem (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *sent;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
sent = g_bytes_new ("Marmalaade!", 11);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", sent);
|
||||
g_bytes_unref (sent);
|
||||
cockpit_channel_close (tc->channel, "boooyah");
|
||||
|
||||
/* Wait until channel closes */
|
||||
while (tc->channel_problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/* Should have sent no payload and control */
|
||||
g_assert_cmpstr (tc->channel_problem, ==, "boooyah");
|
||||
g_assert (mock_transport_pop_channel (tc->transport, "548") == NULL);
|
||||
expect_control_message (mock_transport_pop_control (tc->transport), "ready", "548", NULL);
|
||||
expect_control_message (mock_transport_pop_control (tc->transport),
|
||||
"close", "548", "problem", "boooyah", NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_recv_invalid (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GBytes *converted;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpint (g_socket_send (tc->conn_sock, "\x00Marmalaade!\x00", 13, NULL, &error), ==, 13);
|
||||
g_assert_no_error (error);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
converted = g_bytes_new ("\xef\xbf\xbd""Marmalaade!""\xef\xbf\xbd", 17);
|
||||
g_assert (g_bytes_equal (converted, mock_transport_pop_channel (tc->transport, "548")));
|
||||
g_bytes_unref (converted);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
add_remainder (gpointer user_data)
|
||||
{
|
||||
GSocket *socket = user_data;
|
||||
GError *error = NULL;
|
||||
g_assert_cmpint (g_socket_send (socket, "\x94\x80", 2, NULL, &error), ==, 2);
|
||||
g_assert_no_error (error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
print_gbytes (GBytes *bytes)
|
||||
{
|
||||
gsize i, len;
|
||||
const char* data;
|
||||
|
||||
data = g_bytes_get_data (bytes, &len);
|
||||
g_assert (data);
|
||||
for (i = 0; i < len; ++i)
|
||||
g_printf ("%X", data[i]);
|
||||
puts("");
|
||||
}
|
||||
|
||||
static void
|
||||
test_send_invalid (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *converted;
|
||||
GBytes *sent;
|
||||
|
||||
sent = g_bytes_new ("Oh \x00Marma\x00laade!", 16);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", sent);
|
||||
g_bytes_unref (sent);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
converted = g_bytes_new ("Oh \xef\xbf\xbd""Marma""\xef\xbf\xbd""laade!", 20);
|
||||
g_assert (g_bytes_equal (converted, mock_transport_pop_channel (tc->transport, "548")));
|
||||
g_bytes_unref (converted);
|
||||
}
|
||||
|
||||
static void
|
||||
test_recv_valid_batched (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GBytes *converted;
|
||||
GBytes *received;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpint (g_socket_send (tc->conn_sock, "Marmalaade!\xe2", 12, NULL, &error), ==, 12);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_timeout_add (100, add_remainder, tc->conn_sock);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
converted = g_bytes_new ("Marmalaade!\xe2\x94\x80", 14);
|
||||
received = mock_transport_combine_output (tc->transport, "548", NULL);
|
||||
if (!g_bytes_equal (converted, received))
|
||||
{
|
||||
g_test_fail ();
|
||||
puts ("ERROR: unexpected output\nconverted:");
|
||||
print_gbytes (converted);
|
||||
puts ("received:");
|
||||
print_gbytes (received);
|
||||
}
|
||||
g_bytes_unref (converted);
|
||||
g_bytes_unref (received);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fail_not_found (void)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem = NULL;
|
||||
|
||||
cockpit_expect_message ("*couldn't connect*");
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
channel = cockpit_packet_channel_open (transport, "1", "/non-existent");
|
||||
g_assert (channel != NULL);
|
||||
|
||||
/* Even through failure is on open, should not have closed yet */
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
|
||||
while (problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpstr (problem, ==, "not-found");
|
||||
g_free (problem);
|
||||
g_object_unref (channel);
|
||||
g_object_unref (transport);
|
||||
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_fail_access_denied (void)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *unix_path;
|
||||
gchar *problem = NULL;
|
||||
gint fd;
|
||||
|
||||
if (geteuid () == 0)
|
||||
{
|
||||
g_test_skip ("running as root");
|
||||
return;
|
||||
}
|
||||
|
||||
cockpit_expect_message ("*couldn't connect*");
|
||||
|
||||
unix_path = g_strdup ("/tmp/cockpit-test-XXXXXX.sock");
|
||||
fd = g_mkstemp (unix_path);
|
||||
g_assert_cmpint (fd, >=, 0);
|
||||
|
||||
/* Take away all permissions from the file */
|
||||
g_assert_cmpint (fchmod (fd, 0000), ==, 0);
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
channel = cockpit_packet_channel_open (transport, "1", unix_path);
|
||||
g_assert (channel != NULL);
|
||||
|
||||
/* Even through failure is on open, should not have closed yet */
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
|
||||
while (problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpstr (problem, ==, "access-denied");
|
||||
g_free (problem);
|
||||
g_free (unix_path);
|
||||
close (fd);
|
||||
g_object_unref (channel);
|
||||
g_object_unref (transport);
|
||||
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/packet-channel/echo", TestCase, NULL,
|
||||
setup_channel, test_echo, teardown);
|
||||
g_test_add ("/packet-channel/large", TestCase, NULL,
|
||||
setup_channel, test_large, teardown);
|
||||
g_test_add ("/packet-channel/connect-in-progress",
|
||||
TestCase, &fixture_connect_in_progress,
|
||||
setup_channel, test_connect_in_progress, teardown);
|
||||
g_test_add ("/packet-channel/shutdown", TestCase, NULL,
|
||||
setup_channel, test_shutdown, teardown);
|
||||
g_test_add ("/packet-channel/close-normal", TestCase, NULL,
|
||||
setup_channel, test_close_normal, teardown);
|
||||
g_test_add ("/packet-channel/close-problem", TestCase, NULL,
|
||||
setup_channel, test_close_problem, teardown);
|
||||
g_test_add ("/packet-channel/invalid-send", TestCase, NULL,
|
||||
setup_channel, test_send_invalid, teardown);
|
||||
g_test_add ("/packet-channel/invalid-recv", TestCase, NULL,
|
||||
setup_channel, test_recv_invalid, teardown);
|
||||
g_test_add ("/packet-channel/valid-recv-batched", TestCase, NULL,
|
||||
setup_channel, test_recv_valid_batched, teardown);
|
||||
|
||||
g_test_add_func ("/packet-channel/fail/not-found", test_fail_not_found);
|
||||
g_test_add_func ("/packet-channel/fail/access-denied", test_fail_access_denied);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,246 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitpaths.h"
|
||||
|
||||
#include "testlib/cockpittest.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
const gchar *a;
|
||||
const gchar *b;
|
||||
gboolean expect;
|
||||
const gchar *name;
|
||||
} CmpFixture;
|
||||
|
||||
static const CmpFixture fixtures_has_parent[] = {
|
||||
{ "/c", "/c", FALSE, "equal" },
|
||||
{ "/c", "/c/d", FALSE, "child" },
|
||||
{ "/c", "/c/d/e", FALSE, "grand-child" },
|
||||
{ "/c/d", "/c", TRUE, "parent" },
|
||||
{ "/c/d/e", "/c", FALSE, "grand-parent" },
|
||||
{ "/c", "/peer", FALSE, "peer-after" },
|
||||
{ "/c", "/a", FALSE, "peer-before" },
|
||||
{ "/d", "/door", FALSE, "peer-prefix" },
|
||||
{ "/cat", "/c", FALSE, "peer-truncated" },
|
||||
{ "/", "/c", FALSE, "root-child" },
|
||||
{ "/", "/c/d", FALSE, "root-grand-child" },
|
||||
{ "/c", "/", TRUE, "root-parent" },
|
||||
{ "/c/d", "/", FALSE, "root-grand-parent" },
|
||||
};
|
||||
|
||||
static void
|
||||
test_path_has_parent (gconstpointer data)
|
||||
{
|
||||
const CmpFixture *fixture = data;
|
||||
g_assert (cockpit_path_has_parent (fixture->a, fixture->b) == fixture->expect);
|
||||
}
|
||||
|
||||
static const CmpFixture fixtures_has_ancestor[] = {
|
||||
{ "/c", "/c", FALSE, "equal" },
|
||||
{ "/c", "/c/d", FALSE, "child" },
|
||||
{ "/c", "/c/d/e", FALSE, "grand-child" },
|
||||
{ "/c/d", "/c", TRUE, "parent" },
|
||||
{ "/c/d/e", "/c", TRUE, "grand-parent" },
|
||||
{ "/c", "/peer", FALSE, "peer-after" },
|
||||
{ "/c", "/a", FALSE, "peer-before" },
|
||||
{ "/d", "/door", FALSE, "peer-prefix" },
|
||||
{ "/cat", "/c", FALSE, "peer-truncated" },
|
||||
{ "/", "/c", FALSE, "root-child" },
|
||||
{ "/", "/c/d", FALSE, "root-grand-child" },
|
||||
{ "/c", "/", TRUE, "root-parent" },
|
||||
{ "/c/d", "/", TRUE, "root-grand-parent" },
|
||||
};
|
||||
|
||||
static void
|
||||
test_path_has_ancestor (gconstpointer data)
|
||||
{
|
||||
const CmpFixture *fixture = data;
|
||||
g_assert (cockpit_path_has_ancestor (fixture->a, fixture->b) == fixture->expect);
|
||||
}
|
||||
|
||||
static const CmpFixture fixtures_equal_or_ancestor[] = {
|
||||
{ "/c", "/c", TRUE, "equal" },
|
||||
{ "/c", "/c/d", FALSE, "child" },
|
||||
{ "/c", "/c/d/e", FALSE, "grand-child" },
|
||||
{ "/c/d", "/c", TRUE, "parent" },
|
||||
{ "/c/d/e", "/c", TRUE, "grand-parent" },
|
||||
{ "/c", "/peer", FALSE, "peer-after" },
|
||||
{ "/c", "/a", FALSE, "peer-before" },
|
||||
{ "/d", "/door", FALSE, "peer-prefix" },
|
||||
{ "/cat", "/c", FALSE, "peer-truncated" },
|
||||
{ "/", "/c", FALSE, "root-child" },
|
||||
{ "/", "/c/d", FALSE, "root-grand-child" },
|
||||
{ "/c", "/", TRUE, "root-parent" },
|
||||
{ "/c/d", "/", TRUE, "root-grand-parent" },
|
||||
};
|
||||
|
||||
static void
|
||||
test_path_equal_or_ancestor (gconstpointer data)
|
||||
{
|
||||
const CmpFixture *fixture = data;
|
||||
g_assert (cockpit_path_equal_or_ancestor (fixture->a, fixture->b) == fixture->expect);
|
||||
}
|
||||
|
||||
static void
|
||||
test_paths_add_remove (void)
|
||||
{
|
||||
GTree *paths;
|
||||
const gchar *value;
|
||||
const gchar *check;
|
||||
|
||||
paths = cockpit_paths_new ();
|
||||
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/one"), ==, NULL);
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/two"), ==, NULL);
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/three/3"), ==, NULL);
|
||||
g_assert_cmpint (g_tree_nnodes (paths), ==, 0);
|
||||
|
||||
/* Add first value */
|
||||
value = "/one";
|
||||
check = cockpit_paths_add (paths, value);
|
||||
g_assert (value != check); /* reallocated */
|
||||
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/one"), ==, "/one");
|
||||
g_assert (cockpit_paths_contain (paths, "/one") == check);
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/two"), ==, NULL);
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/three/3"), ==, NULL);
|
||||
g_assert_cmpint (g_tree_nnodes (paths), ==, 1);
|
||||
|
||||
/* Add another value */
|
||||
value = cockpit_paths_add (paths, "/two");
|
||||
g_assert (value != NULL);
|
||||
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/one"), ==, "/one");
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/two"), ==, "/two");
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/three/3"), ==, NULL);
|
||||
g_assert_cmpint (g_tree_nnodes (paths), ==, 2);
|
||||
|
||||
/* Add same one again */
|
||||
check = cockpit_paths_add (paths, "/two");
|
||||
g_assert (check == NULL); /* Already present */
|
||||
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/one"), ==, "/one");
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/two"), ==, "/two");
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/three/3"), ==, NULL);
|
||||
g_assert_cmpint (g_tree_nnodes (paths), ==, 2);
|
||||
|
||||
/* Remove first value */
|
||||
g_assert (cockpit_paths_remove (paths, "/one") == TRUE); /* actually removed */
|
||||
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/one"), ==, NULL);
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/two"), ==, "/two");
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/three/3"), ==, NULL);
|
||||
g_assert_cmpint (g_tree_nnodes (paths), ==, 1);
|
||||
|
||||
/* Remove reference to second value */
|
||||
g_assert (cockpit_paths_remove (paths, "/two") == TRUE); /* not actually */
|
||||
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/one"), ==, NULL);
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/two"), ==, NULL);
|
||||
g_assert_cmpstr (cockpit_paths_contain (paths, "/three/3"), ==, NULL);
|
||||
g_assert_cmpint (g_tree_nnodes (paths), ==, 0);
|
||||
|
||||
/* Add something before destroy, to check destructors */
|
||||
cockpit_paths_add (paths, "/three/3");
|
||||
|
||||
g_tree_destroy (paths);
|
||||
}
|
||||
|
||||
static void
|
||||
test_paths_ancestor_descendant (void)
|
||||
{
|
||||
GTree *paths;
|
||||
|
||||
paths = cockpit_paths_new ();
|
||||
|
||||
cockpit_paths_add (paths, "/a");
|
||||
cockpit_paths_add (paths, "/b");
|
||||
cockpit_paths_add (paths, "/c/3");
|
||||
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/0") == FALSE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/z") == FALSE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/a") == TRUE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/a/1") == FALSE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/a1") == FALSE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/azzzzzz") == FALSE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/") == TRUE);
|
||||
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/b"), ==, "/b");
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/b2"), ==, NULL);
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/b/2"), ==, "/b");
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/"), ==, NULL);
|
||||
|
||||
g_assert(cockpit_paths_contain_or_descendant (paths, "/c/3/4") == FALSE);
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/c"), ==, NULL);
|
||||
|
||||
/* Everything */
|
||||
cockpit_paths_add (paths, "/");
|
||||
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/a") == TRUE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/a/1") == FALSE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/a1") == FALSE);
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/") == TRUE);
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/b"), ==, "/b");
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/b2"), ==, "/");
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/b/2"), ==, "/b");
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/"), ==, "/");
|
||||
g_assert (cockpit_paths_contain_or_descendant (paths, "/c/3/4") == FALSE);
|
||||
g_assert_cmpstr (cockpit_paths_contain_or_ancestor (paths, "/c"), ==, "/");
|
||||
|
||||
g_tree_destroy (paths);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gchar *path;
|
||||
guint i;
|
||||
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (fixtures_has_parent); i++)
|
||||
{
|
||||
path = g_strdup_printf ("/paths/has-parent/%s", fixtures_has_parent[i].name);
|
||||
g_test_add_data_func (path, fixtures_has_parent + i, test_path_has_parent);
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (fixtures_has_parent); i++)
|
||||
{
|
||||
path = g_strdup_printf ("/paths/has-ancestor/%s", fixtures_has_ancestor[i].name);
|
||||
g_test_add_data_func (path, fixtures_has_ancestor + i, test_path_has_ancestor);
|
||||
g_free (path);
|
||||
}
|
||||
for (i = 0; i < G_N_ELEMENTS (fixtures_equal_or_ancestor); i++)
|
||||
{
|
||||
path = g_strdup_printf ("/paths/equal-or-ancestor/%s", fixtures_equal_or_ancestor[i].name);
|
||||
g_test_add_data_func (path, fixtures_equal_or_ancestor + i, test_path_equal_or_ancestor);
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
g_test_add_func ("/paths/add-remove", test_paths_add_remove);
|
||||
g_test_add_func ("/paths/ancestor-descendant", test_paths_ancestor_descendant);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,439 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitmetrics.h"
|
||||
#include "cockpitpcpmetrics.h"
|
||||
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <pcp/pmapi.h>
|
||||
#include <pcp/impl.h>
|
||||
#include <pcp/import.h>
|
||||
|
||||
static void
|
||||
init_mock_archives (void)
|
||||
{
|
||||
g_assert (system ("rm -rf mock-archives && mkdir mock-archives") == 0);
|
||||
|
||||
g_assert (pmiStart ("mock-archives/0", 0) >= 0);
|
||||
g_assert (pmiAddMetric ("mock.value", PM_ID_NULL,
|
||||
PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
|
||||
pmiUnits (0, 0, 0, 0, 0, 0)) >= 0);
|
||||
g_assert (pmiPutValue ("mock.value", NULL, "10") >= 0);
|
||||
g_assert (pmiWrite (0, 0) >= 0);
|
||||
g_assert (pmiPutValue ("mock.value", NULL, "11") >= 0);
|
||||
g_assert (pmiWrite (1, 0) >= 0);
|
||||
g_assert (pmiPutValue ("mock.value", NULL, "12") >= 0);
|
||||
g_assert (pmiWrite (2, 0) >= 0);
|
||||
g_assert (pmiEnd () >= 0);
|
||||
|
||||
g_assert (pmiStart ("mock-archives/1", 0) >= 0);
|
||||
g_assert (pmiAddMetric ("mock.value", PM_ID_NULL,
|
||||
PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
|
||||
pmiUnits (0, 0, 0, 0, 0, 0)) >= 0);
|
||||
g_assert (pmiAddMetric ("mock.late", PM_ID_NULL,
|
||||
PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
|
||||
pmiUnits (0, 0, 0, 0, 0, 0)) >= 0);
|
||||
g_assert (pmiPutValue ("mock.value", NULL, "13") >= 0);
|
||||
g_assert (pmiPutValue ("mock.late", NULL, "30") >= 0);
|
||||
g_assert (pmiWrite (3, 0) >= 0);
|
||||
g_assert (pmiPutValue ("mock.value", NULL, "14") >= 0);
|
||||
g_assert (pmiPutValue ("mock.late", NULL, "31") >= 0);
|
||||
g_assert (pmiWrite (4, 0) >= 0);
|
||||
g_assert (pmiPutValue ("mock.value", NULL, "15") >= 0);
|
||||
g_assert (pmiPutValue ("mock.late", NULL, "32") >= 0);
|
||||
g_assert (pmiWrite (5, 0) >= 0);
|
||||
g_assert (pmiEnd () >= 0);
|
||||
|
||||
// Broken archives should be skipped with a warning
|
||||
g_assert (g_file_set_contents ("mock-archives/2.index", "not a pcp index file", -1, NULL));
|
||||
g_assert (g_file_set_contents ("mock-archives/2.meta", "not a pcp meta file", -1, NULL));
|
||||
g_assert (g_file_set_contents ("mock-archives/2.0", "not a pcp sample file", -1, NULL));
|
||||
}
|
||||
|
||||
static void
|
||||
expect_broken_archive_warning (void)
|
||||
{
|
||||
cockpit_expect_warning("*couldn't create pcp archive context for /*/mock-archives/2*");
|
||||
}
|
||||
|
||||
typedef struct AtTeardown {
|
||||
struct AtTeardown *link;
|
||||
void (*func) (void *);
|
||||
void *data;
|
||||
} AtTeardown;
|
||||
|
||||
typedef struct {
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem;
|
||||
gboolean channel_closed;
|
||||
|
||||
AtTeardown *at_teardown;
|
||||
} TestCase;
|
||||
|
||||
static void
|
||||
on_channel_close (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestCase *tc = user_data;
|
||||
g_assert (tc->channel_closed == FALSE);
|
||||
tc->problem = g_strdup (problem);
|
||||
tc->channel_closed = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_transport_closed (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
static void
|
||||
setup (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
tc->transport = mock_transport_new ();
|
||||
g_signal_connect (tc->transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
tc->channel = NULL;
|
||||
tc->at_teardown = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
at_teardown (TestCase *tc, void *func, void *data)
|
||||
{
|
||||
AtTeardown *item = g_new0 (AtTeardown, 1);
|
||||
|
||||
item->func = func;
|
||||
item->data = data;
|
||||
item->link = tc->at_teardown;
|
||||
tc->at_teardown = item;
|
||||
}
|
||||
|
||||
static void
|
||||
setup_metrics_channel_json (TestCase *tc, JsonObject *options)
|
||||
{
|
||||
tc->channel = g_object_new (COCKPIT_TYPE_PCP_METRICS,
|
||||
"transport", tc->transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
tc->channel_closed = FALSE;
|
||||
g_signal_connect (tc->channel, "closed", G_CALLBACK (on_channel_close), tc);
|
||||
cockpit_channel_prepare (tc->channel);
|
||||
|
||||
/* Switch off compression by default. Compression is done by
|
||||
* comparing two floating point values for exact equality, and we
|
||||
* can't guarantee that we get the same behavior everywhere.
|
||||
*/
|
||||
cockpit_metrics_set_compress (COCKPIT_METRICS (tc->channel), FALSE);
|
||||
}
|
||||
|
||||
static GBytes *
|
||||
recv_bytes (TestCase *tc)
|
||||
{
|
||||
GBytes *msg;
|
||||
while ((msg = mock_transport_pop_channel (tc->transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
recv_json_object (TestCase *tc)
|
||||
{
|
||||
GBytes *msg = recv_bytes (tc);
|
||||
JsonObject *res = cockpit_json_parse_bytes (msg, NULL);
|
||||
g_assert (res != NULL);
|
||||
at_teardown (tc, json_object_unref, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static JsonNode *
|
||||
recv_json (TestCase *tc)
|
||||
{
|
||||
GBytes *msg = recv_bytes (tc);
|
||||
gsize length = g_bytes_get_size (msg);
|
||||
JsonNode *res = cockpit_json_parse (g_bytes_get_data (msg, NULL), length, NULL);
|
||||
g_assert (res != NULL);
|
||||
at_teardown (tc, json_node_free, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
cockpit_assert_expected ();
|
||||
|
||||
g_object_unref (tc->transport);
|
||||
|
||||
if (tc->channel)
|
||||
{
|
||||
g_object_add_weak_pointer (G_OBJECT (tc->channel), (gpointer *)&tc->channel);
|
||||
g_object_unref (tc->channel);
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
while (tc->at_teardown)
|
||||
{
|
||||
AtTeardown *item = tc->at_teardown;
|
||||
tc->at_teardown = item->link;
|
||||
item->func (item->data);
|
||||
g_free (item);
|
||||
}
|
||||
|
||||
g_free (tc->problem);
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
json_obj (const gchar *str)
|
||||
{
|
||||
GError *error = NULL;
|
||||
JsonObject *res = cockpit_json_parse_object (str, -1, &error);
|
||||
g_assert_no_error (error);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
assert_sample_msg (const char *domain,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *func,
|
||||
TestCase *tc,
|
||||
const gchar *json_str)
|
||||
{
|
||||
JsonNode *node = recv_json (tc);
|
||||
g_assert_cmpint (json_node_get_node_type (node), ==, JSON_NODE_ARRAY);
|
||||
_cockpit_assert_json_eq_msg (domain, file, line, func, json_node_get_array (node), json_str);
|
||||
}
|
||||
|
||||
#define assert_sample(tc, json) \
|
||||
(assert_sample_msg (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (tc), (json)))
|
||||
|
||||
static void
|
||||
test_metrics_single_archive (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"" BUILDDIR "/mock-archives/0\","
|
||||
" \"metrics\": [ { \"name\": \"mock.value\" } ],"
|
||||
" \"interval\": 1000"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.value\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[10],[11],[12]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_archive_limit (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"" BUILDDIR "/mock-archives/0\","
|
||||
" \"metrics\": [ { \"name\": \"mock.value\" } ],"
|
||||
" \"interval\": 1000,"
|
||||
" \"limit\": 2"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.value\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[10],[11]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_archive_timestamp (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"" BUILDDIR "/mock-archives/0\","
|
||||
" \"metrics\": [ { \"name\": \"mock.value\" } ],"
|
||||
" \"interval\": 1000,"
|
||||
" \"timestamp\": 1000"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.value\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[11],[12]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_archive_timestamp_now (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
time_t now = time (NULL);
|
||||
|
||||
g_assert (pmiStart ("mock-archives/3", 0) >= 0);
|
||||
g_assert (pmiAddMetric ("mock.now", PM_ID_NULL,
|
||||
PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
|
||||
pmiUnits (0, 0, 0, 0, 0, 0)) >= 0);
|
||||
/* one second in the past to one second in the future */
|
||||
g_assert (pmiPutValue ("mock.now", NULL, "41") >= 0);
|
||||
g_assert (pmiWrite (now - 1, 0) >= 0);
|
||||
g_assert (pmiPutValue ("mock.now", NULL, "42") >= 0);
|
||||
g_assert (pmiWrite (now, 0) >= 0);
|
||||
g_assert (pmiPutValue ("mock.now", NULL, "43") >= 0);
|
||||
g_assert (pmiWrite (now + 1, 0) >= 0);
|
||||
|
||||
g_autofree gchar* json = g_strdup_printf("{ \"source\": \"" BUILDDIR "/mock-archives/3\","
|
||||
" \"metrics\": [ { \"name\": \"mock.now\" } ],"
|
||||
" \"interval\": 1000,"
|
||||
" \"timestamp\": %lli000"
|
||||
"}", (long long) now);
|
||||
JsonObject *options = json_obj(json);
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.now\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[42],[43]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_metrics_archive_directory (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
expect_broken_archive_warning();
|
||||
|
||||
JsonObject *meta;
|
||||
JsonObject *options = json_obj("{ \"source\": \"" BUILDDIR "/mock-archives\","
|
||||
" \"metrics\": [ { \"name\": \"mock.value\" } ],"
|
||||
" \"interval\": 1000"
|
||||
"}");
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.value\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
assert_sample (tc, "[[10],[11],[12]]");
|
||||
|
||||
meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.value\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
assert_sample (tc, "[[13],[14],[15]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_archive_directory_timestamp (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
expect_broken_archive_warning();
|
||||
|
||||
JsonObject *meta;
|
||||
JsonObject *options = json_obj("{ \"source\": \"" BUILDDIR "/mock-archives\","
|
||||
" \"metrics\": [ { \"name\": \"mock.value\" } ],"
|
||||
" \"interval\": 1000,"
|
||||
" \"timestamp\": 4000"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.value\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
assert_sample (tc, "[[14],[15]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_archive_directory_late_metric (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
expect_broken_archive_warning();
|
||||
cockpit_expect_message ("*no such metric: mock.late: Unknown metric name*");
|
||||
|
||||
JsonObject *meta;
|
||||
JsonObject *options = json_obj("{ \"source\": \"" BUILDDIR "/mock-archives\","
|
||||
" \"metrics\": [ { \"name\": \"mock.late\" } ],"
|
||||
" \"interval\": 1000"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.late\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
assert_sample (tc, "[[30],[31],[32]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
if (chdir (BUILDDIR) < 0)
|
||||
g_assert_not_reached ();
|
||||
|
||||
init_mock_archives ();
|
||||
|
||||
g_test_add ("/metrics/single-archive", TestCase, NULL,
|
||||
setup, test_metrics_single_archive, teardown);
|
||||
g_test_add ("/metrics/archive-limit", TestCase, NULL,
|
||||
setup, test_metrics_archive_limit, teardown);
|
||||
g_test_add ("/metrics/archive-timestamp", TestCase, NULL,
|
||||
setup, test_metrics_archive_timestamp, teardown);
|
||||
g_test_add ("/metrics/archive-timestamp-now", TestCase, NULL,
|
||||
setup, test_metrics_archive_timestamp_now, teardown);
|
||||
g_test_add ("/metrics/archive-directory", TestCase, NULL,
|
||||
setup, test_metrics_archive_directory, teardown);
|
||||
g_test_add ("/metrics/archive-directory-timestamp", TestCase, NULL,
|
||||
setup, test_metrics_archive_directory_timestamp, teardown);
|
||||
g_test_add ("/metrics/archive-directory-late-metric", TestCase, NULL,
|
||||
setup, test_metrics_archive_directory_late_metric, teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,639 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitmetrics.h"
|
||||
#include "cockpitpcpmetrics.h"
|
||||
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <pcp/pmapi.h>
|
||||
#include <pcp/impl.h>
|
||||
|
||||
#if PM_VERSION_CURRENT < PM_VERSION(4,0,0)
|
||||
#include <pcp/impl.h>
|
||||
#define pmSpecLocalPMDA(command) __pmSpecLocalPMDA(command)
|
||||
#endif
|
||||
|
||||
void (*mock_pmda_control) (const char *cmd, ...);
|
||||
|
||||
static void *
|
||||
init_mock_pmda (void)
|
||||
{
|
||||
if (pmLoadNameSpace (SRCDIR "/src/bridge/mock-pmns") < 0)
|
||||
{
|
||||
g_test_skip ("No PCP\n");
|
||||
exit (0);
|
||||
}
|
||||
|
||||
g_assert (pmSpecLocalPMDA ("clear") == NULL);
|
||||
g_assert (pmSpecLocalPMDA ("add,333,./mock-pmda.so,mock_init") == NULL);
|
||||
|
||||
void *handle = dlopen ("./mock-pmda.so", RTLD_NOW);
|
||||
g_assert (handle != NULL);
|
||||
|
||||
mock_pmda_control = dlsym (handle, "mock_control");
|
||||
g_assert (mock_pmda_control != NULL);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
typedef struct AtTeardown {
|
||||
struct AtTeardown *link;
|
||||
void (*func) (void *);
|
||||
void *data;
|
||||
} AtTeardown;
|
||||
|
||||
typedef struct {
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem;
|
||||
gboolean channel_closed;
|
||||
|
||||
AtTeardown *at_teardown;
|
||||
} TestCase;
|
||||
|
||||
static void
|
||||
on_channel_close (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestCase *tc = user_data;
|
||||
g_assert (tc->channel_closed == FALSE);
|
||||
tc->problem = g_strdup (problem);
|
||||
tc->channel_closed = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_transport_closed (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
static void
|
||||
setup (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
tc->transport = mock_transport_new ();
|
||||
g_signal_connect (tc->transport, "closed", G_CALLBACK (on_transport_closed), NULL);
|
||||
tc->channel = NULL;
|
||||
tc->at_teardown = NULL;
|
||||
|
||||
mock_pmda_control ("reset");
|
||||
}
|
||||
|
||||
static void
|
||||
at_teardown (TestCase *tc, void *func, void *data)
|
||||
{
|
||||
AtTeardown *item = g_new0 (AtTeardown, 1);
|
||||
|
||||
item->func = func;
|
||||
item->data = data;
|
||||
item->link = tc->at_teardown;
|
||||
tc->at_teardown = item;
|
||||
}
|
||||
|
||||
static void
|
||||
setup_metrics_channel_json (TestCase *tc, JsonObject *options)
|
||||
{
|
||||
tc->channel = g_object_new (COCKPIT_TYPE_PCP_METRICS,
|
||||
"transport", tc->transport,
|
||||
"id", "1234",
|
||||
"options", options,
|
||||
NULL);
|
||||
tc->channel_closed = FALSE;
|
||||
g_signal_connect (tc->channel, "closed", G_CALLBACK (on_channel_close), tc);
|
||||
cockpit_channel_prepare (tc->channel);
|
||||
|
||||
/* We work with real timestamps here but we don't want the
|
||||
interpolation to change any of our sample values.
|
||||
*/
|
||||
cockpit_metrics_set_interpolate (COCKPIT_METRICS (tc->channel), FALSE);
|
||||
|
||||
/* Switch off compression by default. Compression is done by
|
||||
* comparing two floating point values for exact equality, and we
|
||||
* can't guarantee that we get the same behavior everywhere.
|
||||
*/
|
||||
cockpit_metrics_set_compress (COCKPIT_METRICS (tc->channel), FALSE);
|
||||
}
|
||||
|
||||
static GBytes *
|
||||
recv_bytes (TestCase *tc)
|
||||
{
|
||||
GBytes *msg;
|
||||
while ((msg = mock_transport_pop_channel (tc->transport, "1234")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
recv_json_object (TestCase *tc)
|
||||
{
|
||||
GBytes *msg = recv_bytes (tc);
|
||||
JsonObject *res = cockpit_json_parse_bytes (msg, NULL);
|
||||
g_assert (res != NULL);
|
||||
at_teardown (tc, json_object_unref, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static JsonNode *
|
||||
recv_json (TestCase *tc)
|
||||
{
|
||||
GBytes *msg = recv_bytes (tc);
|
||||
gsize length = g_bytes_get_size (msg);
|
||||
JsonNode *res = cockpit_json_parse (g_bytes_get_data (msg, NULL), length, NULL);
|
||||
g_assert (res != NULL);
|
||||
at_teardown (tc, json_node_free, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
wait_channel_closed (TestCase *tc)
|
||||
{
|
||||
while (tc->channel_closed == FALSE)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
cockpit_assert_expected ();
|
||||
|
||||
g_object_unref (tc->transport);
|
||||
|
||||
if (tc->channel)
|
||||
{
|
||||
g_object_add_weak_pointer (G_OBJECT (tc->channel), (gpointer *)&tc->channel);
|
||||
g_object_unref (tc->channel);
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
while (tc->at_teardown)
|
||||
{
|
||||
AtTeardown *item = tc->at_teardown;
|
||||
tc->at_teardown = item->link;
|
||||
item->func (item->data);
|
||||
g_free (item);
|
||||
}
|
||||
|
||||
g_free (tc->problem);
|
||||
}
|
||||
|
||||
static JsonObject *
|
||||
json_obj (const gchar *str)
|
||||
{
|
||||
GError *error = NULL;
|
||||
JsonObject *res = cockpit_json_parse_object (str, -1, &error);
|
||||
g_assert_no_error (error);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
assert_sample_msg (const char *domain,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *func,
|
||||
TestCase *tc,
|
||||
const gchar *json_str)
|
||||
{
|
||||
JsonNode *node = recv_json (tc);
|
||||
g_assert_cmpint (json_node_get_node_type (node), ==, JSON_NODE_ARRAY);
|
||||
_cockpit_assert_json_eq_msg (domain, file, line, func, json_node_get_array (node), json_str);
|
||||
}
|
||||
|
||||
#define assert_sample(tc, json) \
|
||||
(assert_sample_msg (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (tc), (json)))
|
||||
|
||||
static void
|
||||
test_metrics_compression (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.value\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
cockpit_metrics_set_compress (COCKPIT_METRICS (tc->channel), TRUE);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.value\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[0]]");
|
||||
assert_sample (tc, "[[]]");
|
||||
assert_sample (tc, "[[]]");
|
||||
mock_pmda_control ("set-value", 0, 1);
|
||||
assert_sample (tc, "[[1]]");
|
||||
assert_sample (tc, "[[]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_units (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.seconds\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.seconds\", \"units\": \"sec\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[60]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_units_conv (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.seconds\", \"units\": \"min\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.seconds\", \"units\": \"min\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[1]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_units_noconv (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
cockpit_expect_log ("cockpit-protocol", G_LOG_LEVEL_MESSAGE,
|
||||
"1234: direct: can't convert metric mock.seconds to units byte");
|
||||
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.seconds\", \"units\": \"byte\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
wait_channel_closed (tc);
|
||||
g_assert_cmpstr (tc->problem, ==, "protocol-error");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_units_funny_conv (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.seconds\", \"units\": \"2 min\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.seconds\", \"units\": \"min*2\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[0.5]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_strings (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.string\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.string\", \"units\": \"\", \"semantics\": \"instant\" } ]");
|
||||
|
||||
assert_sample (tc, "[[false]]");
|
||||
assert_sample (tc, "[[false]]");
|
||||
|
||||
mock_pmda_control ("set-string", "barfoo");
|
||||
|
||||
assert_sample (tc, "[[false]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_simple_instances (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.values\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.values\", \"units\": \"\", \"semantics\": \"instant\", "
|
||||
" \"instances\": [\"red\", \"green\", \"blue\"] "
|
||||
" } ]");
|
||||
|
||||
assert_sample (tc, "[[[0, 0, 0]]]");
|
||||
mock_pmda_control ("set-value", 1, 1);
|
||||
assert_sample (tc, "[[[1, 0, 0]]]");
|
||||
mock_pmda_control ("set-value", 2, 1);
|
||||
assert_sample (tc, "[[[1, 1, 0]]]");
|
||||
mock_pmda_control ("set-value", 3, 1);
|
||||
assert_sample (tc, "[[[1, 1, 1]]]");
|
||||
assert_sample (tc, "[[[1, 1, 1]]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_instance_filter_include (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.values\" } ],"
|
||||
" \"instances\": [ \"red\", \"blue\" ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.values\", \"units\": \"\", \"semantics\": \"instant\", "
|
||||
" \"instances\": [\"red\", \"blue\"] "
|
||||
" } ]");
|
||||
|
||||
assert_sample (tc, "[[[0, 0]]]");
|
||||
mock_pmda_control ("set-value", 3, 1);
|
||||
assert_sample (tc, "[[[0, 1]]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_instance_filter_omit (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.values\" } ],"
|
||||
" \"omit-instances\": [ \"green\" ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.values\", \"units\": \"\", \"semantics\": \"instant\", "
|
||||
" \"instances\": [\"red\", \"blue\"] "
|
||||
" } ]");
|
||||
|
||||
assert_sample (tc, "[[[0, 0]]]");
|
||||
mock_pmda_control ("set-value", 3, 1);
|
||||
assert_sample (tc, "[[[0, 1]]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_instance_dynamic (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *meta;
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.instances\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.instances\", \"units\": \"\", \"semantics\": \"instant\", "
|
||||
" \"instances\": [] "
|
||||
" } ]");
|
||||
|
||||
assert_sample (tc, "[[[]]]");
|
||||
|
||||
mock_pmda_control ("add-instance", "bananas", 5);
|
||||
mock_pmda_control ("add-instance", "milk", 3);
|
||||
|
||||
meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.instances\", \"units\": \"\", \"semantics\": \"instant\", "
|
||||
" \"instances\": [ \"bananas\", \"milk\" ] "
|
||||
" } ]");
|
||||
assert_sample (tc, "[[[ 5, 3 ]]]");
|
||||
assert_sample (tc, "[[[ 5, 3 ]]]");
|
||||
|
||||
mock_pmda_control ("del-instance", "bananas");
|
||||
|
||||
meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.instances\", \"units\": \"\", \"semantics\": \"instant\", "
|
||||
" \"instances\": [ \"milk\" ] "
|
||||
" } ]");
|
||||
assert_sample (tc, "[[[ 3 ]]]");
|
||||
|
||||
mock_pmda_control ("add-instance", "milk", 2);
|
||||
|
||||
assert_sample (tc, "[[[ 2 ]]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_counter (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.counter\", \"derive\": \"delta\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.counter\", \"units\": \"\", \"semantics\": \"counter\", \"derive\": \"delta\" } ]");
|
||||
|
||||
assert_sample (tc, "[[false]]");
|
||||
assert_sample (tc, "[[0]]");
|
||||
assert_sample (tc, "[[0]]");
|
||||
mock_pmda_control ("inc-counter", 5);
|
||||
assert_sample (tc, "[[5]]");
|
||||
assert_sample (tc, "[[0]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_counter64 (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.counter64\", \"derive\": \"delta\" } ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.counter64\", \"units\": \"\", \"semantics\": \"counter\", \"derive\": \"delta\" } ]");
|
||||
|
||||
assert_sample (tc, "[[false]]");
|
||||
assert_sample (tc, "[[0]]");
|
||||
assert_sample (tc, "[[0]]");
|
||||
mock_pmda_control ("inc-counter64", 5);
|
||||
assert_sample (tc, "[[5]]");
|
||||
assert_sample (tc, "[[0]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
static void
|
||||
test_metrics_counter_across_meta (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *options = json_obj("{ \"source\": \"direct\","
|
||||
" \"metrics\": [ { \"name\": \"mock.counter\", \"derive\": \"delta\" },"
|
||||
" { \"name\": \"mock.instances\" }"
|
||||
" ],"
|
||||
" \"interval\": 1"
|
||||
"}");
|
||||
|
||||
setup_metrics_channel_json (tc, options);
|
||||
|
||||
JsonObject *meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.counter\","
|
||||
" \"units\": \"\","
|
||||
" \"semantics\": \"counter\","
|
||||
" \"derive\": \"delta\""
|
||||
" },"
|
||||
" { \"name\": \"mock.instances\","
|
||||
" \"units\": \"\","
|
||||
" \"semantics\": \"instant\","
|
||||
" \"instances\": [] }"
|
||||
"]");
|
||||
|
||||
assert_sample (tc, "[[false,[]]]");
|
||||
assert_sample (tc, "[[0,[]]]");
|
||||
|
||||
/* Add an instance, which triggers a meta message. The counter
|
||||
should be unaffected and return '0'. Since it is still in the
|
||||
same place in the arrays, it might also be compressed away but as
|
||||
it happens, the channel will not compress over any meta message.
|
||||
*/
|
||||
mock_pmda_control ("add-instance", "foo", 12);
|
||||
meta = recv_json_object (tc);
|
||||
cockpit_assert_json_eq (json_object_get_array_member (meta, "metrics"),
|
||||
"[ { \"name\": \"mock.counter\","
|
||||
" \"units\": \"\","
|
||||
" \"semantics\": \"counter\","
|
||||
" \"derive\": \"delta\""
|
||||
" },"
|
||||
" { \"name\": \"mock.instances\","
|
||||
" \"units\": \"\","
|
||||
" \"semantics\": \"instant\","
|
||||
" \"instances\": [ \"foo\" ] }"
|
||||
"]");
|
||||
assert_sample (tc, "[[0,[12]]]");
|
||||
|
||||
json_object_unref (options);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
void *handle;
|
||||
int ret;
|
||||
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
if (chdir (BUILDDIR) < 0)
|
||||
g_assert_not_reached ();
|
||||
|
||||
handle = init_mock_pmda ();
|
||||
|
||||
g_test_add ("/metrics/compression", TestCase, NULL,
|
||||
setup, test_metrics_compression, teardown);
|
||||
|
||||
g_test_add ("/metrics/units", TestCase, NULL,
|
||||
setup, test_metrics_units, teardown);
|
||||
g_test_add ("/metrics/units-conv", TestCase, NULL,
|
||||
setup, test_metrics_units_conv, teardown);
|
||||
g_test_add ("/metrics/units-noconv", TestCase, NULL,
|
||||
setup, test_metrics_units_noconv, teardown);
|
||||
g_test_add ("/metrics/units-funny-conv", TestCase, NULL,
|
||||
setup, test_metrics_units_funny_conv, teardown);
|
||||
|
||||
g_test_add ("/metrics/strings", TestCase, NULL,
|
||||
setup, test_metrics_strings, teardown);
|
||||
|
||||
g_test_add ("/metrics/simple-instances", TestCase, NULL,
|
||||
setup, test_metrics_simple_instances, teardown);
|
||||
g_test_add ("/metrics/instance-filter-include", TestCase, NULL,
|
||||
setup, test_metrics_instance_filter_include, teardown);
|
||||
g_test_add ("/metrics/instance-filter-omit", TestCase, NULL,
|
||||
setup, test_metrics_instance_filter_omit, teardown);
|
||||
g_test_add ("/metrics/instance-dynamic", TestCase, NULL,
|
||||
setup, test_metrics_instance_dynamic, teardown);
|
||||
|
||||
g_test_add ("/metrics/counter", TestCase, NULL,
|
||||
setup, test_metrics_counter, teardown);
|
||||
g_test_add ("/metrics/counter64", TestCase, NULL,
|
||||
setup, test_metrics_counter64, teardown);
|
||||
g_test_add ("/metrics/counter-across-meta", TestCase, NULL,
|
||||
setup, test_metrics_counter_across_meta, teardown);
|
||||
|
||||
ret = g_test_run ();
|
||||
|
||||
dlclose (handle);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,618 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitpeer.h"
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static GType mock_echo_channel_get_type (void) G_GNUC_CONST;
|
||||
|
||||
typedef struct {
|
||||
CockpitChannel parent;
|
||||
gboolean close_called;
|
||||
} MockEchoChannel;
|
||||
|
||||
typedef CockpitChannelClass MockEchoChannelClass;
|
||||
|
||||
G_DEFINE_TYPE (MockEchoChannel, mock_echo_channel, COCKPIT_TYPE_CHANNEL);
|
||||
|
||||
static gboolean
|
||||
on_other_closed (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer data)
|
||||
{
|
||||
gboolean *flag = data;
|
||||
g_assert (*flag == FALSE);
|
||||
*flag = TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
mock_echo_channel_recv (CockpitChannel *channel,
|
||||
GBytes *message)
|
||||
{
|
||||
cockpit_channel_send (channel, message, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
mock_echo_channel_close (CockpitChannel *channel,
|
||||
const gchar *problem)
|
||||
{
|
||||
MockEchoChannel *self = (MockEchoChannel *)channel;
|
||||
self->close_called = TRUE;
|
||||
COCKPIT_CHANNEL_CLASS (mock_echo_channel_parent_class)->close (channel, problem);
|
||||
}
|
||||
|
||||
static void
|
||||
mock_echo_channel_init (MockEchoChannel *self)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
mock_echo_channel_constructed (GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS (mock_echo_channel_parent_class)->constructed (obj);
|
||||
cockpit_channel_ready (COCKPIT_CHANNEL (obj), NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
mock_echo_channel_class_init (MockEchoChannelClass *klass)
|
||||
{
|
||||
CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
object_class->constructed = mock_echo_channel_constructed;
|
||||
channel_class->recv = mock_echo_channel_recv;
|
||||
channel_class->close = mock_echo_channel_close;
|
||||
}
|
||||
|
||||
static CockpitChannel *
|
||||
mock_echo_channel_open (CockpitTransport *transport,
|
||||
const gchar *channel_id)
|
||||
{
|
||||
CockpitChannel *channel;
|
||||
JsonObject *options;
|
||||
|
||||
g_assert (channel_id != NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
channel = g_object_new (mock_echo_channel_get_type (),
|
||||
"transport", transport,
|
||||
"id", channel_id,
|
||||
"options", options,
|
||||
NULL);
|
||||
|
||||
json_object_unref (options);
|
||||
return channel;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
CockpitPeer *peer;
|
||||
} TestCase;
|
||||
|
||||
static gboolean
|
||||
on_transport_control (CockpitTransport *transport,
|
||||
const char *command,
|
||||
const gchar *channel,
|
||||
JsonObject *options,
|
||||
GBytes *message,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestCase *tc = user_data;
|
||||
const gchar *payload;
|
||||
JsonObject *object;
|
||||
GBytes *data;
|
||||
|
||||
g_assert (tc != NULL);
|
||||
|
||||
if (channel && g_str_equal (command, "open"))
|
||||
{
|
||||
if (tc->peer && cockpit_peer_handle (tc->peer, channel, options, message))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Fallback to echo implementation */
|
||||
else if (!tc->channel && cockpit_json_get_string (options, "payload", NULL, &payload) &&
|
||||
payload && g_str_equal (payload, "upper"))
|
||||
{
|
||||
tc->channel = mock_echo_channel_open (transport, channel);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
object = json_object_new ();
|
||||
json_object_set_string_member (object, "command", "close");
|
||||
json_object_set_string_member (object, "channel", channel);
|
||||
json_object_set_string_member (object, "problem", "not-supported");
|
||||
data = cockpit_json_write_bytes (object);
|
||||
cockpit_transport_send (transport, NULL, data);
|
||||
json_object_unref (object);
|
||||
g_bytes_unref (data);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
setup (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
tc->transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
while (g_main_context_iteration (NULL, FALSE));
|
||||
|
||||
/* Connect to fallback implementation */
|
||||
g_signal_connect (tc->transport, "control", G_CALLBACK (on_transport_control), tc);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
cockpit_assert_expected ();
|
||||
|
||||
g_clear_object (&tc->channel);
|
||||
|
||||
if (tc->peer)
|
||||
{
|
||||
g_object_add_weak_pointer (G_OBJECT (tc->peer), (gpointer *)&tc->peer);
|
||||
g_object_unref (tc->peer);
|
||||
g_assert (tc->peer == NULL);
|
||||
}
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (tc->transport), (gpointer *)&tc->transport);
|
||||
g_object_unref (tc->transport);
|
||||
g_assert (tc->transport == NULL);
|
||||
}
|
||||
|
||||
static CockpitPeer *
|
||||
peer_new (MockTransport *transport,
|
||||
const gchar *bridge)
|
||||
{
|
||||
CockpitPeer *peer;
|
||||
JsonObject *object;
|
||||
GError *error = NULL;
|
||||
|
||||
object = cockpit_json_parse_object (bridge, -1, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
peer = cockpit_peer_new (COCKPIT_TRANSPORT (transport), object);
|
||||
json_object_unref (object);
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
static CockpitPeer *
|
||||
mock_peer_simple_new (MockTransport *transport,
|
||||
const gchar *payload)
|
||||
{
|
||||
CockpitPeer *peer;
|
||||
gchar *bridge;
|
||||
|
||||
bridge = g_strdup_printf ("{ \"match\": { \"payload\": \"%s\" }, \"spawn\": [ \"%s\", \"--%s\" ] }",
|
||||
payload, BUILDDIR "/mock-bridge", payload);
|
||||
|
||||
peer = peer_new (transport, bridge);
|
||||
g_free (bridge);
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
static CockpitPeer *
|
||||
mock_peer_fail_new (MockTransport *transport,
|
||||
const gchar *payload,
|
||||
const gchar *problem)
|
||||
{
|
||||
CockpitPeer *peer;
|
||||
gchar *bridge;
|
||||
|
||||
if (problem)
|
||||
{
|
||||
bridge = g_strdup_printf ("{ \"match\": { \"payload\": \"%s\" }, \"spawn\": [ \"/non-existent\" ], \"problem\": \"%s\" }", payload, problem);
|
||||
}
|
||||
else
|
||||
{
|
||||
bridge = g_strdup_printf ("{ \"match\": { \"payload\": \"%s\" }, \"spawn\": [ \"/non-existent\" ] }", payload);
|
||||
}
|
||||
|
||||
peer = peer_new (transport, bridge);
|
||||
g_free (bridge);
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
static void
|
||||
emit_string (TestCase *tc,
|
||||
const gchar *channel,
|
||||
const gchar *string)
|
||||
{
|
||||
GBytes *bytes = g_bytes_new (string, strlen (string));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), channel, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
static void
|
||||
test_simple (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *sent;
|
||||
|
||||
tc->peer = mock_peer_simple_new (tc->transport, "upper");
|
||||
|
||||
/* The filter should ignore this */
|
||||
emit_string (tc, NULL, "{\"command\": \"hello\"}");
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "a", "oh marmalade");
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
|
||||
|
||||
/* The fallback channel was not created */
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_serial (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *sent;
|
||||
|
||||
tc->peer = mock_peer_simple_new (tc->transport, "upper");
|
||||
|
||||
/* The filter should ignore this */
|
||||
emit_string (tc, NULL, "{\"command\": \"hello\"}");
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "a", "oh marmalade");
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
|
||||
|
||||
/* The fallback channel was not created */
|
||||
g_assert (tc->channel == NULL);
|
||||
|
||||
/* Open a second channel */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"b\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "b", "zero g");
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "b")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_bytes_eq (sent, "ZERO G", -1);
|
||||
|
||||
/* The fallback channel was not created */
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_parallel (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *sent;
|
||||
|
||||
tc->peer = mock_peer_simple_new (tc->transport, "upper");
|
||||
|
||||
/* The filter should ignore this */
|
||||
emit_string (tc, NULL, "{\"command\": \"hello\"}");
|
||||
|
||||
/* Open two channels at the same time both bound for the peer */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"b\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "b", "zero g");
|
||||
emit_string (tc, "a", "oh marmalade");
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "b")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_bytes_eq (sent, "ZERO G", -1);
|
||||
|
||||
/* The fallback channel was not created */
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_init_problem (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *sent;
|
||||
|
||||
/* The "lower" channel has no local implementation to fall back to */
|
||||
tc->peer = mock_peer_simple_new (tc->transport, "problem");
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"problem\"}");
|
||||
emit_string (tc, "a", "Oh Marmalade");
|
||||
|
||||
while ((sent = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_json_eq (sent, "{\"command\":\"close\",\"channel\":\"a\",\"problem\":\"canna-do-it\",\"another-field\":\"extra\"}");
|
||||
|
||||
/* The fallback channel was not created */
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_not_supported (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *sent;
|
||||
|
||||
/* The "lower" channel has no local implementation to fall back to */
|
||||
tc->peer = mock_peer_fail_new (tc->transport, "lower", NULL);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"lower\"}");
|
||||
emit_string (tc, "a", "Oh Marmalade");
|
||||
|
||||
while ((sent = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_json_eq (sent, "{\"command\":\"close\",\"channel\":\"a\",\"problem\":\"not-supported\"}");
|
||||
|
||||
/* The fallback channel was not created */
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fail_problem (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
JsonObject *sent;
|
||||
|
||||
tc->peer = mock_peer_fail_new (tc->transport, "lower", "access-denied");
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"lower\"}");
|
||||
emit_string (tc, "a", "Oh Marmalade");
|
||||
|
||||
while ((sent = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_json_eq (sent, "{\"command\":\"close\",\"channel\":\"a\",\"problem\":\"access-denied\"}");
|
||||
|
||||
/* The fallback channel was not created */
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fallback (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *sent;
|
||||
|
||||
/* The "upper" channel has a local implementation to fallback to */
|
||||
tc->peer = mock_peer_fail_new (tc->transport, "upper", NULL);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "a", "Oh MarmaLade");
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/* The fallback just echos */
|
||||
cockpit_assert_bytes_eq (sent, "Oh MarmaLade", -1);
|
||||
|
||||
/* The fallback channel was created */
|
||||
g_assert (tc->channel != NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_reopen (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
const gchar *bridge;
|
||||
GBytes *sent;
|
||||
JsonObject *control;
|
||||
|
||||
bridge = "{ \"match\": { \"payload\": \"upper\" }, \"spawn\": [ \"/" BUILDDIR "/mock-bridge" "\", \"--upper\", \"--count\" ] }";
|
||||
tc->peer = peer_new (tc->transport, bridge);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "a", "Oh MarmaLade");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\",\"count\":0}");
|
||||
control = NULL;
|
||||
|
||||
/* Get a good response */
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
|
||||
|
||||
/* Reset the peer. This closes the channel. */
|
||||
cockpit_peer_reset (tc->peer);
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"a\",\"problem\":\"terminated\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Sending again reopens with count at zero */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "a", "Oh MarmaLade");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\",\"count\":0}");
|
||||
control = NULL;
|
||||
|
||||
/* Get a good response */
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
|
||||
}
|
||||
|
||||
static void
|
||||
test_timeout (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
const gchar *bridge;
|
||||
GBytes *sent;
|
||||
JsonObject *control;
|
||||
CockpitTransport *other = NULL;
|
||||
gboolean closed = FALSE;
|
||||
|
||||
bridge = "{ \"match\": { \"payload\": \"upper\" }, \"timeout\": 1, \"spawn\": [ \"/" BUILDDIR "/mock-bridge" "\", \"--upper\", \"--count\" ] }";
|
||||
tc->peer = peer_new (tc->transport, bridge);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "a", "Oh MarmaLade");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\",\"count\":0}");
|
||||
control = NULL;
|
||||
|
||||
/* Get a good response */
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
|
||||
|
||||
/* Close the channel, timeout should close the peer transport */
|
||||
other = g_object_ref (cockpit_peer_ensure (tc->peer));
|
||||
g_signal_connect (other, "closed", G_CALLBACK (on_other_closed), &closed);
|
||||
emit_string (tc, NULL, "{\"command\": \"close\", \"channel\": \"a\"}");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"a\"}");
|
||||
control = NULL;
|
||||
|
||||
while (!closed)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/* Sending again reopens peer with count at 0 */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, "a", "Oh MarmaLade");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\",\"count\":0}");
|
||||
control = NULL;
|
||||
|
||||
/* Get a good response */
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE", -1);
|
||||
|
||||
g_object_unref (other);
|
||||
}
|
||||
|
||||
static void
|
||||
test_reopen_fail (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
gchar *link;
|
||||
gchar *bridge;
|
||||
GBytes *sent;
|
||||
JsonObject *control;
|
||||
|
||||
link = g_strdup_printf ("%s/mock-bridge", g_get_tmp_dir ());
|
||||
bridge = g_strdup_printf("{ \"match\": { \"payload\": \"lower\" }, \"spawn\": [ \"%s\", \"--lower\" ], \"problem\": \"custom-problem\" }", link);
|
||||
tc->peer = peer_new (tc->transport, bridge);
|
||||
|
||||
if (g_file_test (link, G_FILE_TEST_EXISTS))
|
||||
g_assert (g_unlink (link) == 0);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"lower\"}");
|
||||
emit_string (tc, "a", "Oh Marmalade");
|
||||
|
||||
/* Bridge doesn't exist, get problem */
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"close\", \"channel\": \"a\", \"problem\":\"custom-problem\"}");
|
||||
control = NULL;
|
||||
|
||||
cockpit_peer_reset (tc->peer);
|
||||
g_assert (symlink (BUILDDIR "/mock-bridge", link) == 0);
|
||||
|
||||
/* Bridge exists, channel works */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"lower\"}");
|
||||
emit_string (tc, "a", "Oh MarmaLade");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Get a good response */
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "oh marmalade", -1);
|
||||
|
||||
g_assert (g_unlink (link) == 0);
|
||||
g_free (bridge);
|
||||
g_free (link);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/peer/simple", TestCase, NULL,
|
||||
setup, test_simple, teardown);
|
||||
g_test_add ("/peer/serial", TestCase, NULL,
|
||||
setup, test_serial, teardown);
|
||||
g_test_add ("/peer/parallel", TestCase, NULL,
|
||||
setup, test_parallel, teardown);
|
||||
g_test_add ("/peer/not-supported", TestCase, NULL,
|
||||
setup, test_not_supported, teardown);
|
||||
g_test_add ("/peer/fail-problem", TestCase, NULL,
|
||||
setup, test_fail_problem, teardown);
|
||||
g_test_add ("/peer/init-problem", TestCase, NULL,
|
||||
setup, test_init_problem, teardown);
|
||||
g_test_add ("/peer/fallback", TestCase, NULL,
|
||||
setup, test_fallback, teardown);
|
||||
g_test_add ("/peer/reopen", TestCase, NULL,
|
||||
setup, test_reopen, teardown);
|
||||
g_test_add ("/peer/reopen-fail", TestCase, NULL,
|
||||
setup, test_reopen_fail, teardown);
|
||||
g_test_add ("/peer/timeout", TestCase, NULL,
|
||||
setup, test_timeout, teardown);
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,970 +0,0 @@
|
|||
|
||||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitpipechannel.h"
|
||||
|
||||
#include "common/cockpitjson.h"
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
#include <glib/gstdio.h>
|
||||
#include <gio/gunixsocketaddress.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Test
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
GSocket *listen_sock;
|
||||
GSource *listen_source;
|
||||
GSocket *conn_sock;
|
||||
GSource *conn_source;
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *channel_problem;
|
||||
gchar *unix_path;
|
||||
gchar *temp_file;
|
||||
} TestCase;
|
||||
|
||||
static gboolean
|
||||
on_socket_input (GSocket *socket,
|
||||
GIOCondition cond,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar buffer[1024];
|
||||
GError *error = NULL;
|
||||
gssize ret, wret;
|
||||
|
||||
ret = g_socket_receive (socket, buffer, sizeof (buffer), NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
g_socket_shutdown (socket, FALSE, TRUE, &error);
|
||||
g_assert_no_error (error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_assert (ret > 0);
|
||||
wret = g_socket_send (socket, buffer, ret, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (wret == ret);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_socket_connection (GSocket *socket,
|
||||
GIOCondition cond,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestCase *tc = user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (tc->conn_source == NULL);
|
||||
tc->conn_sock = g_socket_accept (tc->listen_sock, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
tc->conn_source = g_socket_create_source (tc->conn_sock, G_IO_IN, NULL);
|
||||
g_source_set_callback (tc->conn_source, (GSourceFunc)on_socket_input, tc, NULL);
|
||||
g_source_attach (tc->conn_source, NULL);
|
||||
|
||||
/* Only one connection */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
setup (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
GSocketAddress *address;
|
||||
GError *error = NULL;
|
||||
|
||||
tc->unix_path = g_strdup (data);
|
||||
if (tc->unix_path == NULL)
|
||||
{
|
||||
tc->temp_file = g_strdup ("/tmp/cockpit-test-XXXXXX");
|
||||
g_assert (close (g_mkstemp (tc->temp_file)) == 0);
|
||||
tc->unix_path = g_strconcat (tc->temp_file, ".sock", NULL);
|
||||
}
|
||||
|
||||
address = g_unix_socket_address_new (tc->unix_path);
|
||||
|
||||
tc->listen_sock = g_socket_new (G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM,
|
||||
G_SOCKET_PROTOCOL_DEFAULT, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_socket_bind (tc->listen_sock, address, TRUE, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_socket_listen (tc->listen_sock, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
tc->listen_source = g_socket_create_source (tc->listen_sock, G_IO_IN, NULL);
|
||||
g_source_set_callback (tc->listen_source, (GSourceFunc)on_socket_connection, tc, NULL);
|
||||
g_source_attach (tc->listen_source, NULL);
|
||||
|
||||
g_object_unref (address);
|
||||
|
||||
tc->transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_closed_get_problem (CockpitChannel *channel,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar **retval = user_data;
|
||||
g_assert (retval != NULL && *retval == NULL);
|
||||
*retval = g_strdup (problem ? problem : "");
|
||||
}
|
||||
|
||||
static void
|
||||
setup_channel (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
setup (tc, data);
|
||||
tc->channel = cockpit_pipe_channel_open (COCKPIT_TRANSPORT (tc->transport), "548", tc->unix_path);
|
||||
g_signal_connect (tc->channel, "closed", G_CALLBACK (on_closed_get_problem), &tc->channel_problem);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
if (tc->conn_source)
|
||||
{
|
||||
g_source_destroy (tc->conn_source);
|
||||
g_source_unref (tc->conn_source);
|
||||
}
|
||||
if (tc->listen_source)
|
||||
{
|
||||
g_source_destroy (tc->listen_source);
|
||||
g_source_unref (tc->listen_source);
|
||||
}
|
||||
g_clear_object (&tc->listen_sock);
|
||||
g_clear_object (&tc->conn_sock);
|
||||
|
||||
g_unlink (tc->unix_path);
|
||||
g_free (tc->unix_path);
|
||||
g_unlink (tc->temp_file);
|
||||
g_free (tc->temp_file);
|
||||
|
||||
g_object_unref (tc->transport);
|
||||
|
||||
if (tc->channel)
|
||||
{
|
||||
g_object_add_weak_pointer (G_OBJECT (tc->channel), (gpointer *)&tc->channel);
|
||||
g_object_unref (tc->channel);
|
||||
g_assert (tc->channel == NULL);
|
||||
}
|
||||
|
||||
g_free (tc->channel_problem);
|
||||
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
static void
|
||||
expect_control_message (JsonObject *options,
|
||||
const gchar *command,
|
||||
const gchar *expected_channel,
|
||||
...) G_GNUC_NULL_TERMINATED;
|
||||
|
||||
static void
|
||||
expect_control_message (JsonObject *options,
|
||||
const gchar *expected_command,
|
||||
const gchar *expected_channel,
|
||||
...)
|
||||
{
|
||||
const gchar *expect_option;
|
||||
const gchar *expect_value;
|
||||
const gchar *value;
|
||||
JsonNode *node;
|
||||
va_list va;
|
||||
|
||||
g_assert (options != NULL);
|
||||
g_assert_cmpstr (json_object_get_string_member (options, "command"), ==, expected_command);
|
||||
g_assert_cmpstr (json_object_get_string_member (options, "channel"), ==, expected_channel);
|
||||
|
||||
va_start (va, expected_channel);
|
||||
for (;;) {
|
||||
expect_option = va_arg (va, const gchar *);
|
||||
if (!expect_option)
|
||||
break;
|
||||
expect_value = va_arg (va, const gchar *);
|
||||
|
||||
value = NULL;
|
||||
node = json_object_get_member (options, expect_option);
|
||||
if (node && JSON_NODE_HOLDS_VALUE (node) && json_node_get_value_type (node) == G_TYPE_STRING)
|
||||
value = json_node_get_string (node);
|
||||
|
||||
g_assert_cmpstr (value, ==, expect_value);
|
||||
}
|
||||
va_end (va);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_echo (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *payload;
|
||||
GBytes *sent;
|
||||
|
||||
payload = g_bytes_new ("Marmalaade!", 11);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", payload);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
cockpit_assert_bytes_eq (sent, "Marmalaade!", 11);
|
||||
g_bytes_unref (payload);
|
||||
}
|
||||
|
||||
static void
|
||||
test_shutdown (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GError *error = NULL;
|
||||
JsonObject *sent;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/*
|
||||
* Close down the write end of the socket (what
|
||||
* CockpitTextStream is reading from)
|
||||
*/
|
||||
g_socket_shutdown (tc->conn_sock, FALSE, TRUE, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
while (tc->channel_problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpstr (tc->channel_problem, ==, "");
|
||||
sent = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (sent, "ready", "548", NULL);
|
||||
sent = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (sent, "done", "548", NULL);
|
||||
|
||||
sent = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (sent, "close", "548", "problem", NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_close_normal (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *payload;
|
||||
GBytes *sent;
|
||||
JsonObject *control;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
payload = g_bytes_new ("Marmalaade!", 11);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", payload);
|
||||
cockpit_channel_close (tc->channel, NULL);
|
||||
|
||||
/* Wait until channel closes */
|
||||
while (tc->channel_problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/* Should have sent payload and control */
|
||||
g_assert_cmpstr (tc->channel_problem, ==, "");
|
||||
sent = mock_transport_pop_channel (tc->transport, "548");
|
||||
g_assert (sent != NULL);
|
||||
g_assert (g_bytes_equal (sent, payload));
|
||||
g_bytes_unref (payload);
|
||||
|
||||
control = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (control, "ready", "548", NULL);
|
||||
control = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (control, "done", "548", NULL);
|
||||
|
||||
control = mock_transport_pop_control (tc->transport);
|
||||
expect_control_message (control, "close", "548", "problem", NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_close_problem (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *sent;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
sent = g_bytes_new ("Marmalaade!", 11);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", sent);
|
||||
g_bytes_unref (sent);
|
||||
cockpit_channel_close (tc->channel, "boooyah");
|
||||
|
||||
/* Wait until channel closes */
|
||||
while (tc->channel_problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/* Should have sent no payload and control */
|
||||
g_assert_cmpstr (tc->channel_problem, ==, "boooyah");
|
||||
g_assert (mock_transport_pop_channel (tc->transport, "548") == NULL);
|
||||
expect_control_message (mock_transport_pop_control (tc->transport), "ready", "548", NULL);
|
||||
expect_control_message (mock_transport_pop_control (tc->transport),
|
||||
"close", "548", "problem", "boooyah", NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_spawn_simple (void)
|
||||
{
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem = NULL;
|
||||
JsonObject *options;
|
||||
JsonArray *array;
|
||||
GBytes *sent;
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
array = json_array_new ();
|
||||
json_array_add_string_element (array, "/bin/cat");
|
||||
json_object_set_array_member (options, "spawn", array);
|
||||
json_object_set_string_member (options, "payload", "stream");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_PIPE_CHANNEL,
|
||||
"options", options,
|
||||
"id", "548",
|
||||
"transport", transport,
|
||||
NULL);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
while (g_main_context_iteration (NULL, FALSE));
|
||||
json_object_unref (options);
|
||||
|
||||
sent = g_bytes_new ("Marmalaade!", 11);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (transport), "548", sent);
|
||||
cockpit_channel_close (channel, NULL);
|
||||
|
||||
while (mock_transport_count_sent (transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert (g_bytes_equal (sent, mock_transport_pop_channel (transport, "548")));
|
||||
g_bytes_unref (sent);
|
||||
|
||||
while (!problem)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpstr (problem, ==, "");
|
||||
g_object_unref (channel);
|
||||
|
||||
g_free (problem);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_spawn_environ (void)
|
||||
{
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem = NULL;
|
||||
JsonObject *options;
|
||||
JsonArray *array;
|
||||
GString *string;
|
||||
gconstpointer data;
|
||||
gsize len;
|
||||
GBytes *sent;
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
|
||||
array = json_array_new ();
|
||||
json_array_add_string_element (array, "/bin/sh");
|
||||
json_array_add_string_element (array, "-c");
|
||||
json_array_add_string_element (array, "set");
|
||||
json_object_set_array_member (options, "spawn", array);
|
||||
|
||||
array = json_array_new ();
|
||||
json_array_add_string_element (array, "ENVIRON=Marmalaade");
|
||||
json_object_set_array_member (options, "environ", array);
|
||||
|
||||
json_object_set_string_member (options, "payload", "stream");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_PIPE_CHANNEL,
|
||||
"options", options,
|
||||
"id", "548",
|
||||
"transport", transport,
|
||||
NULL);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
json_object_unref (options);
|
||||
|
||||
string = g_string_new ("");
|
||||
while (!problem)
|
||||
{
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
sent = mock_transport_pop_channel (transport, "548");
|
||||
if (sent)
|
||||
{
|
||||
data = g_bytes_get_data (sent, &len);
|
||||
g_string_append_len (string, data, len);
|
||||
}
|
||||
}
|
||||
|
||||
g_assert_cmpstr (problem, ==, "");
|
||||
g_free (problem);
|
||||
|
||||
cockpit_assert_strmatch (string->str, "*ENVIRON=*Marmalaade*");
|
||||
g_string_free (string, TRUE);
|
||||
|
||||
g_object_unref (channel);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_spawn_status (void)
|
||||
{
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem = NULL;
|
||||
JsonObject *options;
|
||||
JsonArray *array;
|
||||
JsonObject *control;
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
|
||||
array = json_array_new ();
|
||||
json_array_add_string_element (array, "/bin/sh");
|
||||
json_array_add_string_element (array, "-c");
|
||||
json_array_add_string_element (array, "exit 5");
|
||||
json_object_set_array_member (options, "spawn", array);
|
||||
|
||||
json_object_set_string_member (options, "payload", "stream");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_PIPE_CHANNEL,
|
||||
"options", options,
|
||||
"id", "548",
|
||||
"transport", transport,
|
||||
NULL);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
json_object_unref (options);
|
||||
|
||||
while (!problem)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
control = mock_transport_pop_control (transport);
|
||||
expect_control_message (control, "ready", "548", NULL);
|
||||
control = mock_transport_pop_control (transport);
|
||||
expect_control_message (control, "done", "548", NULL);
|
||||
|
||||
control = mock_transport_pop_control (transport);
|
||||
expect_control_message (control, "close", "548", "problem", NULL, NULL);
|
||||
g_assert_cmpint (json_object_get_int_member (control, "exit-status"), ==, 5);
|
||||
|
||||
g_free (problem);
|
||||
|
||||
g_object_unref (channel);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_spawn_signal (void)
|
||||
{
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem = NULL;
|
||||
JsonObject *options;
|
||||
JsonArray *array;
|
||||
JsonObject *control;
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
|
||||
array = json_array_new ();
|
||||
json_array_add_string_element (array, "/bin/sh");
|
||||
json_array_add_string_element (array, "-c");
|
||||
json_array_add_string_element (array, "kill $$");
|
||||
json_object_set_array_member (options, "spawn", array);
|
||||
|
||||
json_object_set_string_member (options, "payload", "stream");
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_PIPE_CHANNEL,
|
||||
"options", options,
|
||||
"id", "548",
|
||||
"transport", transport,
|
||||
NULL);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
json_object_unref (options);
|
||||
|
||||
while (!problem)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
control = mock_transport_pop_control (transport);
|
||||
expect_control_message (control, "ready", "548", NULL);
|
||||
control = mock_transport_pop_control (transport);
|
||||
expect_control_message (control, "done", "548", NULL);
|
||||
|
||||
control = mock_transport_pop_control (transport);
|
||||
cockpit_assert_json_eq (control, "{ \"command\": \"close\", \"channel\": \"548\","
|
||||
" \"exit-signal\": \"TERM\"}");
|
||||
|
||||
g_free (problem);
|
||||
|
||||
g_object_unref (channel);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_spawn_pty (void)
|
||||
{
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem = NULL;
|
||||
JsonObject *options;
|
||||
JsonArray *array;
|
||||
GBytes *sent;
|
||||
GString *received;
|
||||
gconstpointer data;
|
||||
gsize len;
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
array = json_array_new ();
|
||||
json_array_add_string_element (array, "/bin/bash");
|
||||
json_array_add_string_element (array, "-i");
|
||||
json_object_set_array_member (options, "spawn", array);
|
||||
json_object_set_string_member (options, "payload", "stream");
|
||||
json_object_set_boolean_member (options, "pty", TRUE);
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_PIPE_CHANNEL,
|
||||
"options", options,
|
||||
"id", "548",
|
||||
"transport", transport,
|
||||
NULL);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
json_object_unref (options);
|
||||
|
||||
sent = g_bytes_new ("echo booyah\nexit\n", 17);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (transport), "548", sent);
|
||||
g_bytes_unref (sent);
|
||||
|
||||
received = g_string_new ("");
|
||||
while (!problem)
|
||||
{
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
sent = mock_transport_pop_channel (transport, "548");
|
||||
if (sent)
|
||||
{
|
||||
data = g_bytes_get_data (sent, &len);
|
||||
g_string_append_len (received, data, len);
|
||||
}
|
||||
}
|
||||
|
||||
cockpit_assert_strmatch (received->str, "*booyah*");
|
||||
g_string_free (received, TRUE);
|
||||
|
||||
g_assert_cmpstr (problem, ==, "");
|
||||
g_object_unref (channel);
|
||||
|
||||
g_free (problem);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_spawn_errors (void)
|
||||
{
|
||||
MockTransport *transport;
|
||||
g_autofree gchar *problem = NULL;
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
// empty spawn array
|
||||
g_autoptr(JsonObject) options = json_object_new ();
|
||||
JsonArray *array = json_array_new ();
|
||||
json_object_set_array_member (options, "spawn", array);
|
||||
json_object_set_string_member (options, "payload", "stream");
|
||||
|
||||
g_autoptr(CockpitChannel) channel = g_object_new (
|
||||
COCKPIT_TYPE_PIPE_CHANNEL,
|
||||
"options", options,
|
||||
"id", "548",
|
||||
"transport", transport,
|
||||
NULL);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
|
||||
while (!problem)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
JsonObject *control = mock_transport_pop_control (transport);
|
||||
expect_control_message (control, "close", "548", "problem", "protocol-error", NULL);
|
||||
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
|
||||
/* Create GBytes with the contents of @s, without terminating \0 */
|
||||
static GBytes *
|
||||
bytes_from_string (const gchar *s)
|
||||
{
|
||||
gsize len;
|
||||
|
||||
len = strlen (s);
|
||||
return g_bytes_new (s, len);
|
||||
}
|
||||
|
||||
static void
|
||||
test_spawn_pty_resize (void)
|
||||
{
|
||||
MockTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem = NULL;
|
||||
JsonObject *options;
|
||||
JsonArray *array;
|
||||
JsonObject *window;
|
||||
GBytes *sent;
|
||||
GString *received;
|
||||
gconstpointer data;
|
||||
gsize len;
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
|
||||
options = json_object_new ();
|
||||
array = json_array_new ();
|
||||
json_array_add_string_element (array, "/bin/bash");
|
||||
json_array_add_string_element (array, "-i");
|
||||
json_object_set_array_member (options, "spawn", array);
|
||||
json_object_set_string_member (options, "payload", "stream");
|
||||
json_object_set_boolean_member (options, "pty", TRUE);
|
||||
window = json_object_new ();
|
||||
json_object_set_int_member (window, "rows", 1234);
|
||||
json_object_set_int_member (window, "cols", 4567);
|
||||
json_object_set_object_member (options, "window", window);
|
||||
|
||||
channel = g_object_new (COCKPIT_TYPE_PIPE_CHANNEL,
|
||||
"options", options,
|
||||
"id", "548",
|
||||
"transport", transport,
|
||||
NULL);
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
json_object_unref (options);
|
||||
|
||||
/*
|
||||
* Set shell option `checkwinsize`, which tells bash to update $LINES
|
||||
* and $COLUMNS after each command.
|
||||
*/
|
||||
sent = bytes_from_string ("echo -e \"\\x7b$(stty size)\\x7d\"\n");
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (transport), "548", sent);
|
||||
g_bytes_unref (sent);
|
||||
|
||||
received = g_string_new ("");
|
||||
while (!problem)
|
||||
{
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
sent = mock_transport_pop_channel (transport, "548");
|
||||
if (sent)
|
||||
{
|
||||
data = g_bytes_get_data (sent, &len);
|
||||
g_string_append_len (received, data, len);
|
||||
|
||||
if (memchr (data, '}', len))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cockpit_assert_strmatch (received->str, "*{1234 4567}*");
|
||||
|
||||
options = json_object_new ();
|
||||
window = json_object_new ();
|
||||
json_object_set_int_member (window, "rows", 24);
|
||||
json_object_set_int_member (window, "cols", 42);
|
||||
json_object_set_object_member (options, "window", window);
|
||||
|
||||
/* HACK: https://bugzilla.redhat.com/show_bug.cgi?id=1693179
|
||||
* setting PTY size sometimes gets ignored, so retry a few times */
|
||||
for (int retry = 1;; ++retry)
|
||||
{
|
||||
cockpit_transport_emit_control (COCKPIT_TRANSPORT (transport), "options", "548", options, NULL);
|
||||
|
||||
sent = bytes_from_string ("echo -e \"\\x7b$(stty size)\\x7d\"\n");
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (transport), "548", sent);
|
||||
g_bytes_unref (sent);
|
||||
|
||||
g_string_truncate (received, 0);
|
||||
while (!problem)
|
||||
{
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
sent = mock_transport_pop_channel (transport, "548");
|
||||
if (sent)
|
||||
{
|
||||
data = g_bytes_get_data (sent, &len);
|
||||
g_string_append_len (received, data, len);
|
||||
if (memchr (data, '}', len))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (strstr (received->str, "{24 42}"))
|
||||
break;
|
||||
|
||||
g_message ("setting PTY size failed, retry #%i: %s", retry, received->str);
|
||||
|
||||
if (retry == 5)
|
||||
{
|
||||
g_warning ("repeatedly failed to set terminal size for stream channel: %s", received->str);
|
||||
g_test_fail ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sent = bytes_from_string ("exit\n");
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (transport), "548", sent);
|
||||
g_bytes_unref (sent);
|
||||
while (!problem)
|
||||
{
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
mock_transport_pop_channel (transport, "548");
|
||||
}
|
||||
|
||||
json_object_unref (options);
|
||||
g_string_free (received, TRUE);
|
||||
|
||||
g_assert_cmpstr (problem, ==, "");
|
||||
g_object_unref (channel);
|
||||
|
||||
g_free (problem);
|
||||
g_object_unref (transport);
|
||||
}
|
||||
|
||||
static void
|
||||
test_send_invalid (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GBytes *converted;
|
||||
GBytes *sent;
|
||||
|
||||
sent = g_bytes_new ("Oh \x00Marma\x00laade!", 16);
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), "548", sent);
|
||||
g_bytes_unref (sent);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
converted = g_bytes_new ("Oh \xef\xbf\xbd""Marma""\xef\xbf\xbd""laade!", 20);
|
||||
g_assert (g_bytes_equal (converted, mock_transport_pop_channel (tc->transport, "548")));
|
||||
g_bytes_unref (converted);
|
||||
}
|
||||
|
||||
static void
|
||||
test_recv_invalid (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GBytes *converted;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpint (g_socket_send (tc->conn_sock, "\x00Marmalaade!\x00", 13, NULL, &error), ==, 13);
|
||||
g_assert_no_error (error);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
converted = g_bytes_new ("\xef\xbf\xbd""Marmalaade!""\xef\xbf\xbd", 17);
|
||||
g_assert (g_bytes_equal (converted, mock_transport_pop_channel (tc->transport, "548")));
|
||||
g_bytes_unref (converted);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
add_remainder (gpointer user_data)
|
||||
{
|
||||
GSocket *socket = user_data;
|
||||
GError *error = NULL;
|
||||
g_assert_cmpint (g_socket_send (socket, "\x94\x80", 2, NULL, &error), ==, 2);
|
||||
g_assert_no_error (error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
print_gbytes (GBytes *bytes)
|
||||
{
|
||||
gsize i, len;
|
||||
const char* data;
|
||||
|
||||
data = g_bytes_get_data (bytes, &len);
|
||||
g_assert (data);
|
||||
for (i = 0; i < len; ++i)
|
||||
g_printf ("%X", data[i]);
|
||||
puts("");
|
||||
}
|
||||
|
||||
static void
|
||||
test_recv_valid_batched (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GBytes *converted;
|
||||
GBytes *received;
|
||||
|
||||
/* Wait until the socket has opened */
|
||||
while (tc->conn_sock == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpint (g_socket_send (tc->conn_sock, "Marmalaade!\xe2", 12, NULL, &error), ==, 12);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_timeout_add (100, add_remainder, tc->conn_sock);
|
||||
|
||||
while (mock_transport_count_sent (tc->transport) < 2)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
converted = g_bytes_new ("Marmalaade!\xe2\x94\x80", 14);
|
||||
received = mock_transport_combine_output (tc->transport, "548", NULL);
|
||||
if (!g_bytes_equal (converted, received))
|
||||
{
|
||||
g_test_fail ();
|
||||
puts ("ERROR: unexpected output\nconverted:");
|
||||
print_gbytes (converted);
|
||||
puts ("received:");
|
||||
print_gbytes (received);
|
||||
}
|
||||
g_bytes_unref (converted);
|
||||
g_bytes_unref (received);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fail_not_found (void)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *problem = NULL;
|
||||
|
||||
cockpit_expect_log ("cockpit-protocol", G_LOG_LEVEL_MESSAGE, "*couldn't connect*");
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
channel = cockpit_pipe_channel_open (transport, "1", "/non-existent");
|
||||
g_assert (channel != NULL);
|
||||
|
||||
/* Even through failure is on open, should not have closed yet */
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
|
||||
while (problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpstr (problem, ==, "not-found");
|
||||
g_free (problem);
|
||||
g_object_unref (channel);
|
||||
g_object_unref (transport);
|
||||
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_fail_access_denied (void)
|
||||
{
|
||||
CockpitTransport *transport;
|
||||
CockpitChannel *channel;
|
||||
gchar *unix_path;
|
||||
gchar *problem = NULL;
|
||||
gint fd;
|
||||
|
||||
if (geteuid () == 0)
|
||||
{
|
||||
g_test_skip ("running as root");
|
||||
return;
|
||||
}
|
||||
|
||||
cockpit_expect_log ("cockpit-protocol", G_LOG_LEVEL_MESSAGE, "*couldn't connect*");
|
||||
|
||||
unix_path = g_strdup ("/tmp/cockpit-test-XXXXXX.sock");
|
||||
fd = g_mkstemp (unix_path);
|
||||
g_assert_cmpint (fd, >=, 0);
|
||||
|
||||
/* Take away all permissions from the file */
|
||||
g_assert_cmpint (fchmod (fd, 0000), ==, 0);
|
||||
|
||||
transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
channel = cockpit_pipe_channel_open (transport, "1", unix_path);
|
||||
g_assert (channel != NULL);
|
||||
|
||||
/* Even through failure is on open, should not have closed yet */
|
||||
g_signal_connect (channel, "closed", G_CALLBACK (on_closed_get_problem), &problem);
|
||||
|
||||
while (problem == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
g_assert_cmpstr (problem, ==, "access-denied");
|
||||
g_free (problem);
|
||||
g_free (unix_path);
|
||||
close (fd);
|
||||
g_object_unref (channel);
|
||||
g_object_unref (transport);
|
||||
|
||||
cockpit_assert_expected ();
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/pipe-channel/echo", TestCase, NULL,
|
||||
setup_channel, test_echo, teardown);
|
||||
g_test_add ("/pipe-channel/shutdown", TestCase, NULL,
|
||||
setup_channel, test_shutdown, teardown);
|
||||
g_test_add ("/pipe-channel/close-normal", TestCase, NULL,
|
||||
setup_channel, test_close_normal, teardown);
|
||||
g_test_add ("/pipe-channel/close-problem", TestCase, NULL,
|
||||
setup_channel, test_close_problem, teardown);
|
||||
g_test_add ("/pipe-channel/invalid-send", TestCase, NULL,
|
||||
setup_channel, test_send_invalid, teardown);
|
||||
g_test_add ("/pipe-channel/invalid-recv", TestCase, NULL,
|
||||
setup_channel, test_recv_invalid, teardown);
|
||||
g_test_add ("/pipe-channel/valid-recv-batched", TestCase, NULL,
|
||||
setup_channel, test_recv_valid_batched, teardown);
|
||||
|
||||
g_test_add_func ("/pipe-channel/spawn/signal", test_spawn_signal);
|
||||
g_test_add_func ("/pipe-channel/spawn/simple", test_spawn_simple);
|
||||
g_test_add_func ("/pipe-channel/spawn/status", test_spawn_status);
|
||||
g_test_add_func ("/pipe-channel/spawn/environ", test_spawn_environ);
|
||||
g_test_add_func ("/pipe-channel/spawn/pty", test_spawn_pty);
|
||||
g_test_add_func ("/pipe-channel/spawn/pty-resize", test_spawn_pty_resize);
|
||||
g_test_add_func ("/pipe-channel/spawn/errors", test_spawn_errors);
|
||||
|
||||
g_test_add_func ("/pipe-channel/fail/not-found", test_fail_not_found);
|
||||
g_test_add_func ("/pipe-channel/fail/access-denied", test_fail_access_denied);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusinternal.h"
|
||||
|
||||
#include "testlib/cockpittest.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
typedef struct {
|
||||
GDBusConnection *connection;
|
||||
} TestCase;
|
||||
|
||||
static void
|
||||
setup (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
cockpit_dbus_internal_startup (FALSE);
|
||||
|
||||
cockpit_dbus_process_startup ();
|
||||
while (g_main_context_iteration (NULL, FALSE));
|
||||
|
||||
tc->connection = cockpit_dbus_internal_client();
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
cockpit_assert_expected ();
|
||||
g_object_unref (tc->connection);
|
||||
cockpit_dbus_internal_cleanup ();
|
||||
}
|
||||
|
||||
static void
|
||||
on_complete_get_result (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GAsyncResult **ret = user_data;
|
||||
g_assert (ret != NULL);
|
||||
g_assert (*ret == NULL);
|
||||
*ret = g_object_ref (result);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
dbus_call_with_main_loop (TestCase *tc,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *method_name,
|
||||
GVariant *parameters,
|
||||
const GVariantType *reply_type,
|
||||
GError **error)
|
||||
{
|
||||
GAsyncResult *result = NULL;
|
||||
GVariant *retval;
|
||||
|
||||
g_dbus_connection_call (tc->connection, NULL, object_path,
|
||||
interface_name, method_name, parameters,
|
||||
reply_type, G_DBUS_CALL_FLAGS_NONE, -1,
|
||||
NULL, on_complete_get_result, &result);
|
||||
|
||||
while (result == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
retval = g_dbus_connection_call_finish (tc->connection, result, error);
|
||||
g_object_unref (result);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
test_get_properties (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GVariant *retval;
|
||||
GVariant *inner = NULL;
|
||||
GVariant *variable = NULL;
|
||||
gchar **environ = g_listenv ();
|
||||
gint i;
|
||||
|
||||
GError *error = NULL;
|
||||
|
||||
retval = dbus_call_with_main_loop (tc, "/bridge", "org.freedesktop.DBus.Properties", "GetAll",
|
||||
g_variant_new ("(s)", "cockpit.Process"),
|
||||
G_VARIANT_TYPE ("(a{sv})"), &error);
|
||||
|
||||
g_assert_no_error (error);
|
||||
|
||||
inner = g_variant_get_child_value (retval, 0);
|
||||
variable = g_variant_lookup_value (inner, "Environment", G_VARIANT_TYPE ("a{ss}"));
|
||||
for (i = 0; environ[i] != NULL; i++)
|
||||
{
|
||||
gchar *value = NULL;
|
||||
g_assert_true (g_variant_lookup (variable, environ[i], "&s", &value));
|
||||
g_assert_cmpstr (g_getenv (environ[i]), ==, value);
|
||||
}
|
||||
g_variant_unref (variable);
|
||||
|
||||
variable = g_variant_lookup_value (inner, "Pid", G_VARIANT_TYPE ("u"));
|
||||
g_assert (variable != NULL);
|
||||
g_assert_cmpuint (g_variant_get_uint32 (variable), ==, getpid ());
|
||||
g_variant_unref (variable);
|
||||
|
||||
variable = g_variant_lookup_value (inner, "Uid", G_VARIANT_TYPE ("i"));
|
||||
g_assert (variable != NULL);
|
||||
g_assert_cmpuint (g_variant_get_int32 (variable), ==, getuid ());
|
||||
g_variant_unref (variable);
|
||||
|
||||
variable = g_variant_lookup_value (inner, "SessionId", G_VARIANT_TYPE ("s"));
|
||||
g_assert (variable != NULL);
|
||||
/* Not always a valid string during testing */
|
||||
g_variant_unref (variable);
|
||||
|
||||
variable = g_variant_lookup_value (inner, "StartTime", G_VARIANT_TYPE ("t"));
|
||||
g_assert (variable != NULL);
|
||||
g_assert_cmpuint (g_variant_get_uint64 (variable), !=, 0);
|
||||
g_variant_unref (variable);
|
||||
|
||||
g_variant_unref (inner);
|
||||
g_variant_unref (retval);
|
||||
g_strfreev (environ);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/process/get-properties", TestCase, NULL,
|
||||
setup, test_get_properties, teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,895 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2017 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitrouter.h"
|
||||
#include "cockpitdbusinternal.h"
|
||||
|
||||
#include "common/cockpitchannel.h"
|
||||
#include "common/cockpitjson.h"
|
||||
#include "testlib/cockpittest.h"
|
||||
#include "testlib/mock-channel.h"
|
||||
#include "testlib/mock-transport.h"
|
||||
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Mock override from cockpitrouter.c */
|
||||
extern guint cockpit_router_bridge_timeout;
|
||||
|
||||
typedef struct {
|
||||
MockTransport *transport;
|
||||
JsonObject *mock_match;
|
||||
JsonObject *mock_config;
|
||||
GDBusConnection *connection;
|
||||
} TestCase;
|
||||
|
||||
typedef struct {
|
||||
const gchar *payload;
|
||||
gboolean with_env;
|
||||
gboolean privileged;
|
||||
const gchar *problem;
|
||||
const gchar *bridge;
|
||||
} TestFixture;
|
||||
|
||||
static void
|
||||
setup (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
const TestFixture *fixture = user_data;
|
||||
JsonArray *argv;
|
||||
gchar *argument;
|
||||
const gchar *payload;
|
||||
const gchar *bridge;
|
||||
|
||||
tc->mock_config = json_object_new ();
|
||||
argv = json_array_new ();
|
||||
|
||||
bridge = BUILDDIR "/mock-bridge";
|
||||
if (fixture && fixture->bridge)
|
||||
bridge = fixture->bridge;
|
||||
json_array_add_string_element (argv, bridge);
|
||||
|
||||
payload = "upper";
|
||||
if (fixture && fixture->payload)
|
||||
payload = fixture->payload;
|
||||
argument = g_strdup_printf ("--%s", payload);
|
||||
json_array_add_string_element (argv, argument);
|
||||
g_free (argument);
|
||||
json_object_set_array_member (tc->mock_config, "spawn", argv);
|
||||
if (fixture && fixture->privileged)
|
||||
{
|
||||
json_object_set_boolean_member (tc->mock_config, "privileged", TRUE);
|
||||
}
|
||||
json_object_seal (tc->mock_config);
|
||||
|
||||
tc->mock_match = json_object_new ();
|
||||
json_object_set_string_member (tc->mock_match, "payload", payload);
|
||||
|
||||
tc->transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
while (g_main_context_iteration (NULL, FALSE));
|
||||
|
||||
cockpit_dbus_internal_startup (FALSE);
|
||||
tc->connection = cockpit_dbus_internal_client();
|
||||
}
|
||||
|
||||
static void
|
||||
setup_dynamic (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
const TestFixture *fixture = user_data;
|
||||
JsonArray *argv;
|
||||
JsonArray *env;
|
||||
JsonObject *match;
|
||||
|
||||
tc->mock_config = json_object_new ();
|
||||
argv = json_array_new ();
|
||||
match = json_object_new ();
|
||||
|
||||
json_array_add_string_element (argv, BUILDDIR "/mock-bridge");
|
||||
json_array_add_string_element (argv, "--${payload}");
|
||||
json_array_add_string_element (argv, "--count");
|
||||
|
||||
json_object_set_array_member (tc->mock_config, "spawn", argv);
|
||||
|
||||
if (fixture && fixture->problem)
|
||||
json_object_set_string_member (tc->mock_config, "problem", fixture->problem);
|
||||
|
||||
if (fixture && fixture->with_env)
|
||||
{
|
||||
env = json_array_new ();
|
||||
json_array_add_string_element (env, "COCKPIT_TEST_PARAM_ENV=${payload}");
|
||||
json_object_set_array_member (tc->mock_config, "environ", env);
|
||||
}
|
||||
json_object_seal (tc->mock_config);
|
||||
|
||||
json_object_set_null_member (match, "payload");
|
||||
json_object_set_object_member (tc->mock_config, "match", match);
|
||||
|
||||
tc->transport = g_object_new (mock_transport_get_type (), NULL);
|
||||
while (g_main_context_iteration (NULL, FALSE));
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
cockpit_assert_expected ();
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (tc->transport), (gpointer *)&tc->transport);
|
||||
g_object_unref (tc->transport);
|
||||
g_assert (tc->transport == NULL);
|
||||
|
||||
json_object_unref (tc->mock_config);
|
||||
if (tc->mock_match)
|
||||
json_object_unref (tc->mock_match);
|
||||
}
|
||||
|
||||
static void
|
||||
emit_string (TestCase *tc,
|
||||
const gchar *channel,
|
||||
const gchar *string)
|
||||
{
|
||||
GBytes *bytes = g_bytes_new (string, strlen (string));
|
||||
cockpit_transport_emit_recv (COCKPIT_TRANSPORT (tc->transport), channel, bytes);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
static void
|
||||
on_transport_closed (CockpitTransport *transport,
|
||||
const gchar *problem,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar **retval = user_data;
|
||||
g_assert (*retval == NULL);
|
||||
*retval = g_strdup (problem);
|
||||
}
|
||||
|
||||
static void
|
||||
test_local_channel (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
GBytes *sent;
|
||||
|
||||
static CockpitPayloadType payload_types[] = {
|
||||
{ "echo", mock_echo_channel_get_type },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), payload_types, NULL);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"echo\"}");
|
||||
emit_string (tc, "a", "oh marmalade");
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "oh marmalade", -1);
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static void
|
||||
test_external_bridge (TestCase *tc,
|
||||
gconstpointer unused)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
GBytes *sent;
|
||||
JsonObject *control;
|
||||
CockpitTransport *shim_transport = NULL;
|
||||
gchar *problem = NULL;
|
||||
CockpitPeer *peer;
|
||||
|
||||
/* Same argv as used by mock_shim */
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
peer = cockpit_peer_new (COCKPIT_TRANSPORT (tc->transport), tc->mock_config);
|
||||
cockpit_router_add_peer (router, tc->mock_match, peer);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"b\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"c\", \"payload\": \"upper\"}");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"a\"}");
|
||||
control = NULL;
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"b\"}");
|
||||
control = NULL;
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"ready\",\"channel\":\"c\"}");
|
||||
control = NULL;
|
||||
|
||||
emit_string (tc, "a", "oh marmalade a");
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE A", -1);
|
||||
sent = NULL;
|
||||
|
||||
/* Get a ref of the shim transport */
|
||||
shim_transport = cockpit_peer_ensure (peer);
|
||||
g_object_ref (shim_transport);
|
||||
g_signal_connect (shim_transport, "closed", G_CALLBACK (on_transport_closed), &problem);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"close\", \"channel\": \"a\" }");
|
||||
emit_string (tc, "b", "oh marmalade b");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"a\"}");
|
||||
control = NULL;
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "b")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE B", -1);
|
||||
g_assert_null (problem);
|
||||
sent = NULL;
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"close-later\", \"channel\": \"b\" }");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"b\",\"problem\":\"closed\"}");
|
||||
control = NULL;
|
||||
|
||||
g_object_unref (peer);
|
||||
g_object_unref (router);
|
||||
g_object_unref (shim_transport);
|
||||
}
|
||||
|
||||
static const TestFixture fixture_fail = {
|
||||
.payload = "bad"
|
||||
};
|
||||
|
||||
static void
|
||||
test_external_fail (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
JsonObject *received;
|
||||
CockpitPeer *peer;
|
||||
|
||||
g_assert (user_data == &fixture_fail);
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
peer = cockpit_peer_new (COCKPIT_TRANSPORT (tc->transport), tc->mock_config);
|
||||
cockpit_router_add_peer (router, tc->mock_match, peer);
|
||||
g_object_unref (peer);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"bad\"}");
|
||||
emit_string (tc, "a", "oh marmalade");
|
||||
|
||||
while ((received = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_json_eq (received, "{\"command\": \"close\", \"channel\": \"a\", \"problem\": \"not-supported\"}");
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static const TestFixture fixture_dyn_fail = {
|
||||
.problem = "bad"
|
||||
};
|
||||
|
||||
static void
|
||||
test_dynamic_fail (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
JsonObject *received;
|
||||
|
||||
g_assert (user_data == &fixture_dyn_fail);
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
cockpit_router_add_bridge (router, tc->mock_config);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"bad\"}");
|
||||
emit_string (tc, "a", "oh marmalade");
|
||||
|
||||
while ((received = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
cockpit_assert_json_eq (received, "{\"command\": \"close\", \"channel\": \"a\", \"problem\": \"bad\"}");
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
|
||||
static const TestFixture fixture_env = {
|
||||
.with_env = TRUE
|
||||
};
|
||||
|
||||
static void
|
||||
check_ready (JsonObject *control,
|
||||
const gchar *channel,
|
||||
const gchar *payload,
|
||||
gint count,
|
||||
gboolean with_env)
|
||||
{
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "channel"), ==, channel);
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "command"), ==, "ready");
|
||||
g_assert_cmpint (json_object_get_int_member (control, "count"), ==, count);
|
||||
if (with_env)
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "test-env"), ==, payload);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dynamic_bridge (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
const TestFixture *fixture = user_data;
|
||||
CockpitRouter *router;
|
||||
GBytes *sent;
|
||||
JsonObject *control;
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
cockpit_router_add_bridge (router, tc->mock_config);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"b\", \"payload\": \"upper\"}");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
check_ready (control, "a", "upper", 0, fixture ? fixture->with_env : FALSE);
|
||||
control = NULL;
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
check_ready (control, "b", "upper", 1, fixture ? fixture->with_env : FALSE);
|
||||
control = NULL;
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"c\", \"payload\": \"lower\"}");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
check_ready (control, "c", "lower", 0, fixture ? fixture->with_env : FALSE);
|
||||
control = NULL;
|
||||
|
||||
emit_string (tc, "a", "oh marmalade a");
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE A", -1);
|
||||
sent = NULL;
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"close\", \"channel\": \"a\" }");
|
||||
emit_string (tc, "b", "oh marmalade b");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"a\"}");
|
||||
control = NULL;
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "b")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "OH MARMALADE B", -1);
|
||||
sent = NULL;
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"close\", \"channel\": \"b\" }");
|
||||
emit_string (tc, "c", "OH MARMALADE C");
|
||||
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"b\"}");
|
||||
control = NULL;
|
||||
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "c")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "oh marmalade c", -1);
|
||||
sent = NULL;
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"close-later\", \"channel\": \"c\" }");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"close\",\"channel\":\"c\",\"problem\":\"closed\"}");
|
||||
control = NULL;
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static const TestFixture fixture_host = {
|
||||
.payload = "host",
|
||||
.bridge = BUILDDIR "/mock-echo"
|
||||
};
|
||||
|
||||
static void
|
||||
check_unchanged_host (TestCase *tc,
|
||||
const gchar *host)
|
||||
{
|
||||
JsonObject *control;
|
||||
gchar *msg = g_strdup_printf ("{\"command\": \"open\", \"channel\": \"a%s\", \"payload\": \"host\", \"host\": \"%s\"}", host, host);
|
||||
|
||||
emit_string (tc, NULL, msg);
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, msg);
|
||||
control = NULL;
|
||||
g_free (msg);
|
||||
}
|
||||
|
||||
static void
|
||||
test_host_processing (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
JsonObject *control;
|
||||
CockpitRouter *router;
|
||||
CockpitPeer *peer;
|
||||
|
||||
g_assert (user_data == &fixture_host);
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
peer = cockpit_peer_new (COCKPIT_TRANSPORT (tc->transport), tc->mock_config);
|
||||
cockpit_router_add_peer (router, tc->mock_match, peer);
|
||||
g_object_unref (peer);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
check_unchanged_host (tc, "host");
|
||||
check_unchanged_host (tc, "host+");
|
||||
check_unchanged_host (tc, "host+key");
|
||||
check_unchanged_host (tc, "host+key+");
|
||||
|
||||
/* Test localhost is removed */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"localhost\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Test host-key1 is set to value */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"host+key1+value\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"open\",\"channel\":\"a\",\"payload\":\"host\",\"host\":\"host\",\"host-key1\":\"value\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Test with + in value */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"host+key1+value+value\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"open\",\"channel\":\"a\",\"payload\":\"host\",\"host\":\"host\",\"host-key1\":\"value+value\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Test localhost is removed but host-key1 present */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"localhost+key1+value\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"open\",\"channel\":\"a\",\"payload\":\"host\",\"host-key1\":\"value\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Test doesn't replace host-key1 */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"localhost+key1+value\",\"host-key1\":\"extra\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\":\"open\",\"channel\":\"a\",\"payload\":\"host\",\"host\":\"localhost+key1+value\",\"host-key1\":\"extra\"}");
|
||||
control = NULL;
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static void
|
||||
test_sharable_processing (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
JsonObject *control;
|
||||
CockpitRouter *router;
|
||||
CockpitPeer *peer;
|
||||
|
||||
g_assert (user_data == &fixture_host);
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
peer = cockpit_peer_new (COCKPIT_TRANSPORT (tc->transport), tc->mock_config);
|
||||
cockpit_router_add_peer (router, tc->mock_match, peer);
|
||||
g_object_unref (peer);
|
||||
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
|
||||
/* Test host-key is private */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"localhost\", \"host-key\": \"host-key\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host-key\": \"host-key\", \"session\": \"private\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Test user is private */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"localhost\", \"user\": \"user\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"user\": \"user\", \"session\": \"private\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Test user with temp-session false is not private */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"localhost\", \"user\": \"user\", \"temp-session\": false}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"user\": \"user\"}");
|
||||
control = NULL;
|
||||
|
||||
/* Test user with shareable is not touched */
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"host\":\"localhost\", \"user\": \"user\", \"session\": \"other\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"host\", \"user\": \"user\", \"session\": \"other\"}");
|
||||
control = NULL;
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static GList *
|
||||
make_bridge_configs (const gchar *payload, ...)
|
||||
{
|
||||
GList *configs = NULL;
|
||||
va_list ap;
|
||||
va_start (ap, payload);
|
||||
|
||||
while (payload)
|
||||
{
|
||||
const gchar *arg = va_arg (ap, const gchar *);
|
||||
|
||||
JsonObject *match = json_object_new ();
|
||||
json_object_set_string_member (match, "payload", payload);
|
||||
JsonArray *spawn = json_array_new ();
|
||||
json_array_add_string_element (spawn, BUILDDIR "/mock-bridge");
|
||||
while (arg)
|
||||
{
|
||||
json_array_add_string_element (spawn, arg);
|
||||
arg = va_arg (ap, const gchar *);
|
||||
}
|
||||
|
||||
JsonObject *config = json_object_new ();
|
||||
json_object_set_object_member (config, "match", match);
|
||||
json_object_set_array_member (config, "spawn", spawn);
|
||||
json_object_seal (config);
|
||||
|
||||
configs = g_list_prepend (configs, config);
|
||||
|
||||
payload = va_arg (ap, const gchar *);
|
||||
}
|
||||
|
||||
va_end (ap);
|
||||
return g_list_reverse (configs);
|
||||
}
|
||||
|
||||
static void
|
||||
free_bridge_configs (GList *configs)
|
||||
{
|
||||
g_list_free_full (configs, (GDestroyNotify)json_object_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
test_reload_add (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
GList *configs;
|
||||
JsonObject *control;
|
||||
GBytes *sent;
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
|
||||
// Configure only the "upper" payload
|
||||
configs = make_bridge_configs ("upper", "--upper", NULL,
|
||||
NULL);
|
||||
cockpit_router_set_bridges (router, configs);
|
||||
free_bridge_configs (configs);
|
||||
|
||||
// Open a "upper" channel
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "channel"), ==, "a");
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "command"), ==, "ready");
|
||||
control = NULL;
|
||||
|
||||
// And check that it works
|
||||
emit_string (tc, "a", "before reload");
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "BEFORE RELOAD", -1);
|
||||
sent = NULL;
|
||||
|
||||
// Try to open a "lower" channel and check that this is rejected
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"b\", \"payload\": \"lower\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "channel"), ==, "b");
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "command"), ==, "close");
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "problem"), ==, "not-supported");
|
||||
control = NULL;
|
||||
|
||||
// Reconfigure and add the "lower" payload
|
||||
configs = make_bridge_configs ("upper", "--upper", NULL,
|
||||
"lower", "--lower", NULL,
|
||||
NULL);
|
||||
cockpit_router_set_bridges (router, configs);
|
||||
free_bridge_configs (configs);
|
||||
|
||||
// Check that the "upper" channel still works
|
||||
emit_string (tc, "a", "after reload");
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "AFTER RELOAD", -1);
|
||||
sent = NULL;
|
||||
|
||||
// Open a "lower" channel
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"c\", \"payload\": \"lower\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "channel"), ==, "c");
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "command"), ==, "ready");
|
||||
control = NULL;
|
||||
|
||||
// And check that it now works
|
||||
emit_string (tc, "c", "NEW PAYLOAD");
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "c")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "new payload", -1);
|
||||
sent = NULL;
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static void
|
||||
test_reload_remove (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
GList *configs;
|
||||
JsonObject *control;
|
||||
GBytes *sent;
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
emit_string (tc, NULL, "{\"command\": \"init\", \"version\": 1, \"host\": \"localhost\" }");
|
||||
|
||||
// Configure the "upper" payload
|
||||
configs = make_bridge_configs ("upper", "--upper", NULL,
|
||||
NULL);
|
||||
cockpit_router_set_bridges (router, configs);
|
||||
free_bridge_configs (configs);
|
||||
|
||||
// Open a "upper" channel
|
||||
emit_string (tc, NULL, "{\"command\": \"open\", \"channel\": \"a\", \"payload\": \"upper\"}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "channel"), ==, "a");
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "command"), ==, "ready");
|
||||
control = NULL;
|
||||
|
||||
// And check that it works
|
||||
emit_string (tc, "a", "before reload");
|
||||
while ((sent = mock_transport_pop_channel (tc->transport, "a")) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_bytes_eq (sent, "BEFORE RELOAD", -1);
|
||||
sent = NULL;
|
||||
|
||||
// Reconfigure and remove the "upper" payload
|
||||
configs = make_bridge_configs (NULL);
|
||||
cockpit_router_set_bridges (router, configs);
|
||||
free_bridge_configs (configs);
|
||||
|
||||
// Check that the "upper" channel has been closed
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "channel"), ==, "a");
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "command"), ==, "close");
|
||||
g_assert_cmpstr (json_object_get_string_member (control, "problem"), ==, "terminated");
|
||||
control = NULL;
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static void
|
||||
on_complete_get_result (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GAsyncResult **ret = user_data;
|
||||
g_assert (ret != NULL);
|
||||
g_assert (*ret == NULL);
|
||||
*ret = g_object_ref (result);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
dbus_call_with_main_loop (TestCase *tc,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *method_name,
|
||||
GVariant *parameters,
|
||||
const GVariantType *reply_type,
|
||||
GError **error)
|
||||
{
|
||||
GAsyncResult *result = NULL;
|
||||
GVariant *retval;
|
||||
|
||||
g_dbus_connection_call (tc->connection, NULL, object_path,
|
||||
interface_name, method_name, parameters,
|
||||
reply_type, G_DBUS_CALL_FLAGS_NONE, -1,
|
||||
NULL, on_complete_get_result, &result);
|
||||
|
||||
while (result == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
retval = g_dbus_connection_call_finish (tc->connection, result, error);
|
||||
g_object_unref (result);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static const TestFixture fixture_superuser = {
|
||||
.privileged = TRUE
|
||||
};
|
||||
|
||||
static void
|
||||
test_superuser_none (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
JsonObject *control;
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
cockpit_router_dbus_startup (router);
|
||||
|
||||
cockpit_router_add_bridge (router, tc->mock_config);
|
||||
emit_string (tc, NULL, "{'command': 'init', 'version': 1, 'host': 'localhost', 'superuser': false }");
|
||||
|
||||
// Superuser channels should be rejected
|
||||
//
|
||||
emit_string (tc, NULL, "{'command': 'open', 'channel': 'a', 'payload': 'upper', 'superuser': true}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{'command':'close','channel':'a', 'problem':'access-denied'}");
|
||||
control = NULL;
|
||||
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static void
|
||||
test_superuser_get_all (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
GError *error = NULL;
|
||||
GVariant *retval;
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
cockpit_router_dbus_startup (router);
|
||||
|
||||
cockpit_router_add_bridge (router, tc->mock_config);
|
||||
emit_string (tc, NULL, "{'command': 'init', 'version': 1, 'host': 'localhost', 'superuser': false }");
|
||||
|
||||
retval = dbus_call_with_main_loop (tc, "/superuser", "org.freedesktop.DBus.Properties", "GetAll",
|
||||
g_variant_new ("(s)", "cockpit.Superuser"),
|
||||
G_VARIANT_TYPE ("(a{sv})"), &error);
|
||||
|
||||
g_assert_no_error (error);
|
||||
cockpit_assert_gvariant_eq (retval, "({'Bridges': <['mock-bridge']>, 'Current': <'none'>},)");
|
||||
|
||||
g_variant_unref (retval);
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_superuser_current (TestCase *tc,
|
||||
const gchar *expected)
|
||||
{
|
||||
GVariant *retval;
|
||||
GError *error = NULL;
|
||||
gchar *expected_variant;
|
||||
|
||||
retval = dbus_call_with_main_loop (tc, "/superuser", "org.freedesktop.DBus.Properties", "Get",
|
||||
g_variant_new ("(ss)", "cockpit.Superuser", "Current"),
|
||||
G_VARIANT_TYPE ("(v)"), &error);
|
||||
g_assert_no_error (error);
|
||||
expected_variant = g_strdup_printf ("(<'%s'>,)", expected);
|
||||
cockpit_assert_gvariant_eq (retval, expected_variant);
|
||||
g_free (expected_variant);
|
||||
g_variant_unref (retval);
|
||||
}
|
||||
|
||||
static void
|
||||
test_superuser_start (TestCase *tc,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
CockpitRouter *router;
|
||||
GError *error = NULL;
|
||||
JsonObject *control;
|
||||
GVariant *retval;
|
||||
|
||||
router = cockpit_router_new (COCKPIT_TRANSPORT (tc->transport), NULL, NULL);
|
||||
cockpit_router_dbus_startup (router);
|
||||
|
||||
cockpit_router_add_bridge (router, tc->mock_config);
|
||||
emit_string (tc, NULL, "{'command': 'init', 'version': 1, 'host': 'localhost', 'superuser': false }");
|
||||
|
||||
assert_superuser_current (tc, "none");
|
||||
|
||||
retval = dbus_call_with_main_loop (tc, "/superuser", "cockpit.Superuser", "Start",
|
||||
g_variant_new ("(s)", "mock-bridge"),
|
||||
G_VARIANT_TYPE ("()"), &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
assert_superuser_current (tc, "mock-bridge");
|
||||
|
||||
// Superuser channels should now work
|
||||
//
|
||||
emit_string (tc, NULL, "{'command': 'open', 'channel': 'a', 'payload': 'upper', 'superuser': true}");
|
||||
while ((control = mock_transport_pop_control (tc->transport)) == NULL)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
cockpit_assert_json_eq (control, "{'command':'ready','channel':'a'}");
|
||||
control = NULL;
|
||||
|
||||
g_variant_unref (retval);
|
||||
g_object_unref (router);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/router/local-channel", TestCase, NULL,
|
||||
setup, test_local_channel, teardown);
|
||||
g_test_add ("/router/external-bridge", TestCase, NULL,
|
||||
setup, test_external_bridge, teardown);
|
||||
g_test_add ("/router/external-fail", TestCase, &fixture_fail,
|
||||
setup, test_external_fail, teardown);
|
||||
g_test_add ("/router/dynamic-bridge-fail", TestCase, &fixture_dyn_fail,
|
||||
setup_dynamic, test_dynamic_fail, teardown);
|
||||
g_test_add ("/router/dynamic-bridge", TestCase, NULL,
|
||||
setup_dynamic, test_dynamic_bridge, teardown);
|
||||
g_test_add ("/router/dynamic-bridge-env", TestCase, &fixture_env,
|
||||
setup_dynamic, test_dynamic_bridge, teardown);
|
||||
g_test_add ("/router/host-processing", TestCase, &fixture_host,
|
||||
setup, test_host_processing, teardown);
|
||||
g_test_add ("/router/sharable-processing", TestCase, &fixture_host,
|
||||
setup, test_sharable_processing, teardown);
|
||||
|
||||
g_test_add ("/router/reload/add", TestCase, NULL,
|
||||
setup, test_reload_add, teardown);
|
||||
g_test_add ("/router/reload/remove", TestCase, NULL,
|
||||
setup, test_reload_remove, teardown);
|
||||
|
||||
g_test_add ("/router/superuser/none", TestCase, &fixture_superuser,
|
||||
setup, test_superuser_none, teardown);
|
||||
g_test_add ("/router/superuser/get-all", TestCase, &fixture_superuser,
|
||||
setup, test_superuser_get_all, teardown);
|
||||
g_test_add ("/router/superuser/start", TestCase, &fixture_superuser,
|
||||
setup, test_superuser_start, teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "cockpitdbusrules.h"
|
||||
|
||||
#include "testlib/cockpittest.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
CockpitDBusRules *rules;
|
||||
} TestCase;
|
||||
|
||||
typedef struct {
|
||||
const gchar *path;
|
||||
gboolean is_namespace;
|
||||
const gchar *interface;
|
||||
const gchar *member;
|
||||
const gchar *arg0;
|
||||
} TestRule;
|
||||
|
||||
static const TestRule default_rules[] = {
|
||||
{ "/otree", TRUE, NULL, NULL, NULL },
|
||||
{ "/scruffy/the/janitor", FALSE, NULL, "Marmalade", NULL },
|
||||
{ "/planetexpress", TRUE, "org.PlanetExpress.Interface", NULL, NULL },
|
||||
{ "/arg", FALSE, NULL, NULL, "Durn" },
|
||||
{ NULL, }
|
||||
};
|
||||
|
||||
static const TestRule empty_rules[] = {
|
||||
{ NULL, }
|
||||
};
|
||||
|
||||
static const TestRule path_rules[] = {
|
||||
{ "/otree", TRUE, NULL, NULL, NULL },
|
||||
{ "/scruffy/the/janitor", FALSE, NULL, NULL, NULL },
|
||||
{ "/planetexpress", TRUE, NULL, NULL, NULL },
|
||||
{ "/arg", FALSE, NULL, NULL, NULL },
|
||||
{ NULL, }
|
||||
};
|
||||
|
||||
static void
|
||||
setup (TestCase *test,
|
||||
gconstpointer data)
|
||||
{
|
||||
const TestRule *rules = data;
|
||||
gint i;
|
||||
|
||||
if (!rules)
|
||||
rules = default_rules;
|
||||
|
||||
test->rules = cockpit_dbus_rules_new ();
|
||||
for (i = 0; rules[i].path != NULL; i++)
|
||||
{
|
||||
cockpit_dbus_rules_add (test->rules, rules[i].path, rules[i].is_namespace,
|
||||
rules[i].interface, rules[i].member, rules[i].arg0);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *test,
|
||||
gconstpointer data)
|
||||
{
|
||||
cockpit_assert_expected ();
|
||||
|
||||
cockpit_dbus_rules_free (test->rules);
|
||||
}
|
||||
|
||||
static void
|
||||
test_basics (TestCase *test,
|
||||
gconstpointer fixture)
|
||||
{
|
||||
/* Should all match, only based on path */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", "org.Interface", NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", "org.Interface", "Signal", NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", "org.Interface", NULL, "arg") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", NULL, "Signal", "arg") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", "org.Interface", "Signal", "arg") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/bark", "org.Interface", "Signal", "arg") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree", "org.Interface", "Signal", "arg") == TRUE);
|
||||
|
||||
/* Mismatched path */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", "org.Interface", NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", "org.Interface", "Signal", NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", "org.Interface", NULL, "arg") == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", NULL, "Signal", "arg") == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", "org.Interface", "Signal", "arg") == FALSE);
|
||||
|
||||
/* Interfaces affect matching */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/planetexpress", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/planetexpress", "org.PlanetExpress.Interface", NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/planetexpress", "other.Interface", NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/planetexpress/sub", "org.PlanetExpress.Interface", NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/planetexpress/sub", "other.Interface", NULL, NULL) == FALSE);
|
||||
|
||||
/* Members affect matching */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/scruffy/the/janitor", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/scruffy/the/janitor", NULL, "Marmalade", NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/scruffy/the/janitor", NULL, "Other", NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/scruffy/the/janitor/sub", NULL, "Marmalade", NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/scruffy/the/janitor/sub", NULL, "Other", NULL) == FALSE);
|
||||
|
||||
/* Args affect matching */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/arg", NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/arg", NULL, NULL, "Durn") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/arg", NULL, NULL, "other") == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/arg/sub", NULL, NULL, "Durn") == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/arg/sub", NULL, NULL, "other") == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/arg/sub", NULL, NULL, NULL) == FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_nothing (TestCase *test,
|
||||
gconstpointer fixture)
|
||||
{
|
||||
/* No rules should never match anything */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/", NULL, NULL, NULL) == FALSE);
|
||||
|
||||
g_assert (cockpit_dbus_rules_remove (test->rules, NULL, FALSE, NULL, NULL, NULL) == FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_path_only (TestCase *test,
|
||||
gconstpointer fixture)
|
||||
{
|
||||
/* Should all match, only based on path */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", "org.Interface", NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", "org.Interface", "Signal", NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", "org.Interface", NULL, "arg") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", NULL, "Signal", "arg") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", "org.Interface", "Signal", "arg") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/bark", "org.Interface", "Signal", "arg") == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree", "org.Interface", "Signal", "arg") == TRUE);
|
||||
|
||||
/* Mismatched path */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", "org.Interface", NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", "org.Interface", "Signal", NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", "org.Interface", NULL, "arg") == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", NULL, "Signal", "arg") == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/not", "org.Interface", "Signal", "arg") == FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_all_paths (TestCase *test,
|
||||
gconstpointer fixture)
|
||||
{
|
||||
cockpit_dbus_rules_add (test->rules, "/", TRUE, NULL, NULL, NULL);
|
||||
|
||||
/* Should all match, only based on path */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/boring", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/tettot", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/aoenut", NULL, NULL, NULL) == TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_null_path (TestCase *test,
|
||||
gconstpointer fixture)
|
||||
{
|
||||
/* Adds a global empty rule which should match everything */
|
||||
cockpit_dbus_rules_add (test->rules, NULL, FALSE, NULL, NULL, NULL);
|
||||
|
||||
/* Should all match, only based on path */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/boring", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/tettot", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/aoenut", NULL, NULL, NULL) == TRUE);
|
||||
|
||||
cockpit_dbus_rules_remove (test->rules, NULL, FALSE, NULL, NULL, NULL);
|
||||
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/otree/blah", NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/boring", NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/tettot", NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/aoenut", NULL, NULL, NULL) == FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_root_only (TestCase *test,
|
||||
gconstpointer fixture)
|
||||
{
|
||||
/* This should only match the root path */
|
||||
cockpit_dbus_rules_add (test->rules, "/", FALSE, NULL, NULL, NULL);
|
||||
|
||||
/* Should all match, only based on path */
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/", NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/boring", NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/tettot", NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_match (test->rules, "/aoenut", NULL, NULL, NULL) == FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_add_ref_remove (TestCase *test,
|
||||
gconstpointer fixture)
|
||||
{
|
||||
const TestRule *rules = default_rules;
|
||||
gint i;
|
||||
|
||||
/* Add all the rules once */
|
||||
for (i = 0; rules[i].path != NULL; i++)
|
||||
{
|
||||
g_assert (cockpit_dbus_rules_add (test->rules, rules[i].path, rules[i].is_namespace,
|
||||
rules[i].interface, rules[i].member, rules[i].arg0) == TRUE);
|
||||
}
|
||||
|
||||
/* Add them again, should always return FALSE here */
|
||||
for (i = 0; rules[i].path != NULL; i++)
|
||||
{
|
||||
g_assert (cockpit_dbus_rules_add (test->rules, rules[i].path, rules[i].is_namespace,
|
||||
rules[i].interface, rules[i].member, rules[i].arg0) == FALSE);
|
||||
}
|
||||
|
||||
/* Add another rule */
|
||||
g_assert (cockpit_dbus_rules_remove (test->rules, "/booo", FALSE, NULL, NULL, NULL) == FALSE);
|
||||
g_assert (cockpit_dbus_rules_add (test->rules, "/booo", FALSE, NULL, NULL, NULL) == TRUE);
|
||||
|
||||
/* Now remove them, the first time shouldn't actually remove */
|
||||
for (i = 0; rules[i].path != NULL; i++)
|
||||
{
|
||||
g_assert (cockpit_dbus_rules_remove (test->rules, rules[i].path, rules[i].is_namespace,
|
||||
rules[i].interface, rules[i].member, rules[i].arg0) == FALSE);
|
||||
}
|
||||
|
||||
/* The second time actually removes */
|
||||
for (i = 0; rules[i].path != NULL; i++)
|
||||
{
|
||||
g_assert (cockpit_dbus_rules_remove (test->rules, rules[i].path, rules[i].is_namespace,
|
||||
rules[i].interface, rules[i].member, rules[i].arg0) == TRUE);
|
||||
}
|
||||
|
||||
g_assert (cockpit_dbus_rules_remove (test->rules, "/booo", FALSE, NULL, NULL, NULL) == TRUE);
|
||||
g_assert (cockpit_dbus_rules_remove (test->rules, "/booo", FALSE, NULL, NULL, NULL) == FALSE);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/rules/basics", TestCase, NULL,
|
||||
setup, test_basics, teardown);
|
||||
g_test_add ("/rules/nothing", TestCase, empty_rules,
|
||||
setup, test_nothing, teardown);
|
||||
g_test_add ("/rules/path-only", TestCase, path_rules,
|
||||
setup, test_path_only, teardown);
|
||||
g_test_add ("/rules/all-paths", TestCase, empty_rules,
|
||||
setup, test_all_paths, teardown);
|
||||
g_test_add ("/rules/root-only", TestCase, empty_rules,
|
||||
setup, test_root_only, teardown);
|
||||
g_test_add ("/rules/null-path", TestCase, empty_rules,
|
||||
setup, test_null_path, teardown);
|
||||
g_test_add ("/rules/add-ref-remove", TestCase, empty_rules,
|
||||
setup, test_add_ref_remove, teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue