app/dbus-helpers: Don't error out if caller is updates driver

If the systemd unit associated with the client's PID is the updates
driver's unit, don't require the --bypass-driver option for operations
like upgrade, deploy, and rebase.
This is useful for updates drivers that shell out to rpm-ostree's
binary (e.g. Zincati, currently).
Also refactor some helper functions to make them more general and
reusable.
This commit is contained in:
Kelvin Fan 2021-02-24 13:39:07 -05:00 committed by OpenShift Merge Robot
parent 705b22df28
commit 1c826e993b
4 changed files with 95 additions and 42 deletions

View File

@ -287,8 +287,8 @@ get_update_driver_state (RPMOSTreeSysroot *sysroot_proxy,
g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy)); g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy));
const char *update_driver_objpath = NULL; const char *update_driver_objpath = NULL;
if (!get_sd_unit_objpath(connection, update_driver_sd_unit, &update_driver_objpath, if (!get_sd_unit_objpath (connection, "LoadUnit", g_variant_new ("(s)", update_driver_sd_unit),
cancellable, error)) &update_driver_objpath, cancellable, error))
return FALSE; return FALSE;
/* Look up ActiveState property of update driver's systemd unit. */ /* Look up ActiveState property of update driver's systemd unit. */

View File

@ -24,6 +24,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <systemd/sd-login.h> #include <systemd/sd-login.h>
#include <utility> #include <utility>
#include <unistd.h>
#include <glib-unix.h> #include <glib-unix.h>
#include <libglnx.h> #include <libglnx.h>
@ -1526,47 +1527,45 @@ rpmostree_print_cached_update (GVariant *cached_update,
return TRUE; return TRUE;
} }
/* Query systemd for update driver's systemd unit's object path. */ /* Query systemd for systemd unit's object path using method_name provided with
* parameters. The reply_type of method_name must be G_VARIANT_TYPE_TUPLE. */
gboolean gboolean
get_sd_unit_objpath (GDBusConnection *connection, get_sd_unit_objpath (GDBusConnection *connection,
const char *update_driver_sd_unit, const char *method_name,
const char **update_driver_objpath, GVariant *parameters,
const char **unit_objpath,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
{ {
g_autoptr(GVariant) update_driver_objpath_tuple = g_autoptr(GVariant) update_driver_objpath_tuple =
g_dbus_connection_call_sync (connection, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", g_dbus_connection_call_sync (connection, "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", "LoadUnit", "org.freedesktop.systemd1.Manager", method_name,
g_variant_new ("(s)", update_driver_sd_unit), G_VARIANT_TYPE_TUPLE, parameters, G_VARIANT_TYPE_TUPLE,
G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error);
if (!update_driver_objpath_tuple) if (!update_driver_objpath_tuple)
return FALSE; return FALSE;
else if (g_variant_n_children (update_driver_objpath_tuple) < 1) else if (g_variant_n_children (update_driver_objpath_tuple) < 1)
return glnx_throw (error, "LoadUnit(%s) returned empty tuple", update_driver_sd_unit); return glnx_throw (error, "%s returned empty tuple", method_name);
g_autoptr(GVariant) update_driver_objpath_val = g_autoptr(GVariant) update_driver_objpath_val =
g_variant_get_child_value (update_driver_objpath_tuple, 0); g_variant_get_child_value (update_driver_objpath_tuple, 0);
*update_driver_objpath = g_variant_dup_string (update_driver_objpath_val, NULL); *unit_objpath = g_variant_dup_string (update_driver_objpath_val, NULL);
g_assert (*update_driver_objpath); g_assert (*unit_objpath);
return TRUE; return TRUE;
} }
/* Get the `Documentation` property of `sd_unit`. Documentation is returned /* Get the property_name property of sd_unit. Returns a reference to the GVariant
* as a string array GVariant in `unit_doc_array`. */ * instance that holds the value for property_name GVariant in cache_val. */
static gboolean static gboolean
get_sd_unit_doc (GDBusConnection *connection, get_sd_unit_property (GDBusConnection *connection,
const char *sd_unit, const char *objpath,
GVariant **unit_doc_array, const char *property_name,
GVariant **cache_val,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
{ {
const char *objpath = NULL; /* Look up property_name property of systemd unit. */
if (!get_sd_unit_objpath (connection, sd_unit, &objpath,
cancellable, error))
return FALSE;
/* Look up `Documentation` property of systemd unit. */
g_autoptr(GDBusProxy) unit_obj_proxy = g_autoptr(GDBusProxy) unit_obj_proxy =
g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL,
"org.freedesktop.systemd1", objpath, "org.freedesktop.systemd1", objpath,
@ -1574,11 +1573,11 @@ get_sd_unit_doc (GDBusConnection *connection,
if (!unit_obj_proxy) if (!unit_obj_proxy)
return FALSE; return FALSE;
*unit_doc_array = *cache_val =
g_dbus_proxy_get_cached_property (unit_obj_proxy, "Documentation"); g_dbus_proxy_get_cached_property (unit_obj_proxy, property_name);
if (!*unit_doc_array) if (!*cache_val)
return glnx_throw (error, "Documentation property not found in proxy's cache (%s)", return glnx_throw (error, "%s property not found in proxy's cache (%s)",
objpath); property_name, objpath);
return TRUE; return TRUE;
} }
@ -1587,22 +1586,22 @@ get_sd_unit_doc (GDBusConnection *connection,
* Prints any errors that occur. */ * Prints any errors that occur. */
static void static void
append_docs_to_str (GString *str, append_docs_to_str (GString *str,
RPMOSTreeSysroot *sysroot_proxy, GDBusConnection *connection,
char *name,
char *sd_unit, char *sd_unit,
GCancellable *cancellable) GCancellable *cancellable)
{ {
g_autoptr(GVariant) docs_array = NULL; g_autoptr(GVariant) docs_array = NULL;
g_autoptr(GError) local_error = NULL; g_autoptr(GError) local_error = NULL;
GDBusConnection *connection = const char *objpath = NULL;
g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy)); if (!get_sd_unit_objpath (connection, "LoadUnit", g_variant_new ("(s)", sd_unit),
if (!get_sd_unit_doc (connection, sd_unit, &docs_array, cancellable, &local_error)) &objpath, cancellable, &local_error))
g_printerr ("%s", local_error->message);
if (!get_sd_unit_property (connection, objpath, "Documentation", &docs_array, cancellable, &local_error))
{ {
g_printerr ("%s", local_error->message); g_printerr ("%s", local_error->message);
} }
else if (docs_array) else if (docs_array)
{ {
g_string_append_printf (str, "See %s's documentation", name);
gsize docs_len; gsize docs_len;
g_autofree const char **docs = g_autofree const char **docs =
g_variant_get_strv (docs_array, &docs_len); g_variant_get_strv (docs_array, &docs_len);
@ -1620,6 +1619,32 @@ append_docs_to_str (GString *str,
} }
} }
/* Check whether sd_unit contains pid and return the boolean in sd_unit_contains_pid */
static gboolean
check_sd_unit_contains_pid (char *sd_unit,
pid_t pid,
gboolean *sd_unit_contains_pid,
GDBusConnection *connection,
GCancellable *cancellable,
GError **error)
{
// Get the systemd unit associated with pid.
const char *objpath = NULL;
g_autoptr(GVariant) process_sd_unit_val = NULL;
const char *process_sd_unit = NULL;
if (!get_sd_unit_objpath (connection, "GetUnitByPID", g_variant_new ("(u)", (guint32) pid),
&objpath, cancellable, error))
return FALSE;
if (!get_sd_unit_property (connection, objpath, "Id", &process_sd_unit_val, cancellable, error))
return FALSE;
process_sd_unit = g_variant_get_string (process_sd_unit_val, NULL);
if (g_strcmp0 (process_sd_unit, sd_unit) == 0)
*sd_unit_contains_pid = TRUE;
else
*sd_unit_contains_pid = FALSE;
return TRUE;
}
/* Throw an error if an updates driver is registered. */ /* Throw an error if an updates driver is registered. */
gboolean gboolean
error_if_driver_registered (GBusType bus_type, error_if_driver_registered (GBusType bus_type,
@ -1636,13 +1661,26 @@ error_if_driver_registered (GBusType bus_type,
* done through the driver. */ * done through the driver. */
if (update_driver_sd_unit && update_driver_name) if (update_driver_sd_unit && update_driver_name)
{ {
GDBusConnection *connection =
g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy));
// Do not error out if current process' systemd unit is the same as updates driver's.
pid_t pid = getpid();
gboolean sd_unit_contains_pid = FALSE;
if (!check_sd_unit_contains_pid (update_driver_sd_unit, pid, &sd_unit_contains_pid,
connection, cancellable, error))
return FALSE;
if (sd_unit_contains_pid)
return TRUE;
// Build and throw error message.
g_autoptr(GString) error_msg = g_string_new(NULL); g_autoptr(GString) error_msg = g_string_new(NULL);
g_string_printf (error_msg, "Updates and deployments are driven by %s (%s)\n", g_string_printf (error_msg, "Updates and deployments are driven by %s (%s)\n",
update_driver_name, update_driver_sd_unit); update_driver_name, update_driver_sd_unit);
g_string_append_printf (error_msg, "See %s's documentation", update_driver_name);
/* only try to get unit's `Documentation` if we're on the system bus */ /* only try to get unit's `Documentation` if we're on the system bus */
if (bus_type == G_BUS_TYPE_SYSTEM) if (bus_type == G_BUS_TYPE_SYSTEM)
append_docs_to_str (error_msg, sysroot_proxy, update_driver_name, append_docs_to_str (error_msg, connection, update_driver_sd_unit, cancellable);
update_driver_sd_unit, cancellable);
g_string_append_printf (error_msg, g_string_append_printf (error_msg,
"Use --bypass-driver to bypass %s and perform the operation anyways", "Use --bypass-driver to bypass %s and perform the operation anyways",
update_driver_name); update_driver_name);

View File

@ -133,8 +133,9 @@ rpmostree_print_cached_update (GVariant *cached_update,
gboolean gboolean
get_sd_unit_objpath (GDBusConnection *connection, get_sd_unit_objpath (GDBusConnection *connection,
const char *update_driver_sd_unit, const char *method_name,
const char **update_driver_objpath, GVariant *parameters,
const char **unit_objpath,
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);

View File

@ -289,16 +289,18 @@ assert_file_has_content status.txt "failed to finalize previous deployment"
assert_file_has_content status.txt "error: opendir" assert_file_has_content status.txt "error: opendir"
echo "ok previous staged failure in status" echo "ok previous staged failure in status"
# Test `deploy --register-driver` option. # Test `deploy --register-driver` option
vm_cmd rpm-ostree deploy \'\' \ # Create and start a transient test-driver.service unit to register our fake driver
vm_cmd systemd-run --unit=test-driver.service --wait -q \
rpm-ostree deploy \'\' \
--register-driver=TestDriver --register-driver=TestDriver
vm_cmd test -f /run/rpm-ostree/update-driver.gv vm_cmd test -f /run/rpm-ostree/update-driver.gv
vm_cmd rpm-ostree status > status.txt vm_cmd rpm-ostree status > status.txt
assert_file_has_content status.txt 'AutomaticUpdatesDriver: TestDriver' assert_file_has_content status.txt 'AutomaticUpdatesDriver: TestDriver'
vm_cmd rpm-ostree status -v > verbose_status.txt vm_cmd rpm-ostree status -v > verbose_status.txt
assert_file_has_content verbose_status.txt 'AutomaticUpdatesDriver: TestDriver (sshd.service)' assert_file_has_content verbose_status.txt 'AutomaticUpdatesDriver: TestDriver (test-driver.service)'
vm_assert_status_jq ".\"update-driver\"[\"driver-name\"] == \"TestDriver\"" \ vm_assert_status_jq ".\"update-driver\"[\"driver-name\"] == \"TestDriver\"" \
".\"update-driver\"[\"driver-sd-unit\"] == \"sshd.service\"" ".\"update-driver\"[\"driver-sd-unit\"] == \"test-driver.service\""
echo "ok deploy --register-driver with empty string revision" echo "ok deploy --register-driver with empty string revision"
# Ensure that we are prevented from rebasing when an updates driver is registered # Ensure that we are prevented from rebasing when an updates driver is registered
@ -333,3 +335,15 @@ vm_rpmostree upgrade --bypass-driver 2>err.txt
assert_not_file_has_content err.txt 'Updates and deployments are driven by TestDriver' assert_not_file_has_content err.txt 'Updates and deployments are driven by TestDriver'
vm_rpmostree cleanup -p vm_rpmostree cleanup -p
echo "ok upgrade when updates driver is registered" echo "ok upgrade when updates driver is registered"
# Test that we don't need to --bypass-driver if the systemd unit associated with
# the client's PID is the update driver's systemd unit.
vm_cmd rpm-ostree deploy \'\' \
--register-driver=OtherTestDriver --bypass-driver
# Make sure OtherTestDriver's systemd unit will be the same as the commandline's
vm_cmd rpm-ostree status -v > verbose_status.txt
assert_file_has_content verbose_status.txt 'AutomaticUpdatesDriver: OtherTestDriver (sshd.service)'
vm_rpmostree upgrade 2>err.txt
assert_not_file_has_content err.txt 'Updates and deployments are driven by OtherTestDriver'
vm_rpmostree cleanup -p
echo "ok upgrade without --bypass-driver when same systemd unit"