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:
Allison Karlitskaya 2024-10-07 12:21:12 +02:00
parent 8463df0251
commit 7c7ab45e3f
107 changed files with 1 additions and 38074 deletions

View file

@ -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

View file

@ -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}

View file

@ -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',

View file

@ -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)

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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);
}
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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

View file

@ -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 */

View file

@ -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)", &section, &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)", &section, &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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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_ */

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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

View file

@ -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_ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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);
}
}
}

View file

@ -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 */

View file

@ -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

View file

@ -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

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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
}

View file

@ -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 ();
}

View file

@ -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 ();
}

View file

@ -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 ();
}

File diff suppressed because it is too large Load diff

View file

@ -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;
}

View file

@ -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

View file

@ -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 ();
}

View file

@ -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 ();
}

View file

@ -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 ();
}

View file

@ -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;
}

View file

@ -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 ();
}

View file

@ -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 ();
}

View file

@ -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 ();
}

View file

@ -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 ();
}

View file

@ -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