/**
* @file packagekit.c
* Mode Control Entity - tracking package upgrade status
*
* Copyright (C) 2014-2019 Jolla Ltd.
*
* @author Simo Piiroinen
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce 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 mce. If not, see .
*/
#include "../mce.h"
#include "../mce-log.h"
#include "../mce-dbus.h"
#include
#include
#include
/* ========================================================================= *
* D-BUS CONSTANTS
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* DBUS DAEMON ITSELF
* ------------------------------------------------------------------------- */
// DBUS_INTERFACE_DBUS "org.freedesktop.DBus"
#define DBUS_DAEMON_REQ_GET_NAME_OWNER "GetNameOwner"
#define DBUS_DAEMON_SIG_NAME_OWNER_CHANGED "NameOwnerChanged"
/* ------------------------------------------------------------------------- *
* DBUS PROPERTIES INTERFACE
* ------------------------------------------------------------------------- */
// DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties"
#define PROPERTIES_REQ_GET "Get"
#define PROPERTIES_REQ_GET_ALL "GetAll"
#define PROPERTIES_REQ_SET "Set"
#define PROPERTIES_SIG_CHANGED "PropertiesChanged"
/* ------------------------------------------------------------------------- *
* PACKAGEKIT
* ------------------------------------------------------------------------- */
#define PKGKIT_SERVICE "org.freedesktop.PackageKit"
#define PKGKIT_INTERFACE "org.freedesktop.PackageKit"
#define PKGKIT_OBJECT "/org/freedesktop/PackageKit"
/* ------------------------------------------------------------------------- *
* SYSTEMD
* ------------------------------------------------------------------------- */
#define SYSTEMD_SERVICE "org.freedesktop.systemd1"
#define SYSTEMD_OBJECT "/org/freedesktop/systemd1"
#define SYSTEMD_MANAGER_INTERFACE "org.freedesktop.systemd1.Manager"
#define SYSTEMD_MANAGER_START_UNIT "StartUnit"
/* ========================================================================= *
* PROTOTYPES
* ========================================================================= */
// STATE_MANAGEMENT
static void xpkgkit_set_locked_state (bool locked);
static void xpkgkit_set_available_state (bool available);
// DBUS_HELPERS
static void xpkgkit_parse_changed_properties(DBusMessageIter *body);
static void xpkgkit_parse_dropped_properties(DBusMessageIter *body);
// DBUS_IPC
static void xpkgkit_get_properties_cb (DBusPendingCall *pc, void*aptr);
static void xpkgkit_get_properties (void);
static void xpkgkit_check_name_owner_cb (DBusPendingCall *pc, void*aptr);
static void xpkgkit_check_name_owner (void);
// UPDATE_LOGGING
static void xpkgkit_logging_request_start_cb(DBusPendingCall *pc, void *aptr);
static void xpkgkit_logging_request_start (void);
static void xpkgkit_logging_cancel_start (void);
// DATAPIPE_HANDLERS
static void xpkgkit_datapipe_osupdate_running_cb(gconstpointer data);
static void xpkgkit_datapipe_init (void);
static void xpkgkit_datapipe_quit (void);
// DBUS_HANDLERS
static gboolean xpkgkit_name_owner_changed_cb (DBusMessage *rsp);
static gboolean xpkgkit_property_changed_cb (DBusMessage *sig);
// MODULE_LOAD_UNLOAD
G_MODULE_EXPORT const gchar *g_module_check_init(GModule *module);
G_MODULE_EXPORT void g_module_unload (GModule *module);
/* ========================================================================= *
* STATE_MANAGEMENT
* ========================================================================= */
/* PackageKit is on D-Bus and Locked property is set */
static bool xpkgkit_is_locked = false;
static void
xpkgkit_set_locked_state(bool locked)
{
if( xpkgkit_is_locked == locked )
goto EXIT;
xpkgkit_is_locked = locked;
mce_log(LL_DEBUG, "packagekit is %slocked", locked ? "" : "not ");
datapipe_exec_full(&packagekit_locked_pipe,
GINT_TO_POINTER(xpkgkit_is_locked));
EXIT:
return;
}
/* PackageKit is on D-Bus */
static bool xpkgkit_is_available = false;
/** Handle "org.freedesktop.PackageKit" D-Bus name owner changes
*
* Clear xpkgkit_is_locked on all changes.
*
* Query properties if there is a new owner.
*
* @param available true if PackageKit name has an owner, false if not
*/
static void
xpkgkit_set_available_state(bool available)
{
if( xpkgkit_is_available == available )
goto EXIT;
mce_log(LL_DEBUG, "%s is %savailable", PKGKIT_SERVICE,
available ? "" : "not ");
/* unlocked until proven otherwise */
xpkgkit_set_locked_state(false);
if( (xpkgkit_is_available = available) ) {
/* start (async) property query */
xpkgkit_get_properties();
}
EXIT:
return;
}
/* ========================================================================= *
* DBUS_HELPERS
* ========================================================================= */
/** Parse array of (string key, variant value) entries
*
* Update xpkgkit_is_locked as needed.
*
* @param body D-Bus message iterator
*/
static void
xpkgkit_parse_changed_properties(DBusMessageIter *body)
{
// initialize to current value
bool locked = xpkgkit_is_locked;
if( !body )
goto EXIT;
DBusMessageIter arr, ent, var;
//
if( !mce_dbus_iter_get_array(body, &arr) )
goto EXIT;
while( !mce_dbus_iter_at_end(&arr) ) {
const char *key = 0;
if( !mce_dbus_iter_get_entry(&arr, &ent) )
goto EXIT;
if( !mce_dbus_iter_get_string(&ent, &key) )
goto EXIT;
if( !mce_dbus_iter_get_variant(&ent, &var) )
goto EXIT;
if( !strcmp(key, "Locked") ) {
bool val = false;
if( !mce_dbus_iter_get_bool(&var, &val) )
goto EXIT;
mce_log(LL_DEBUG, "%s = bool %d", key, val);
locked = val;
}
else {
char *val = mce_dbus_message_iter_repr(&var);
mce_log(LL_DEBUG, "%s = %s", key, val);
free(val);
}
}
EXIT:
xpkgkit_set_locked_state(locked);
return;
}
/** Parse array of dropped property keys
*
* Update xpkgkit_is_locked as needed.
*
* @param body D-Bus message iterator
*/
static void
xpkgkit_parse_dropped_properties(DBusMessageIter *body)
{
// initialize to current value
bool locked = xpkgkit_is_locked;
if( !body )
goto EXIT;
DBusMessageIter arr;
//
if( !mce_dbus_iter_get_array(body, &arr) )
goto EXIT;
while( !mce_dbus_iter_at_end(&arr) ) {
const char *key = 0;
if( !mce_dbus_iter_get_string(&arr, &key) )
goto EXIT;
mce_log(LL_DEBUG, "%s = ", key);
if( !strcmp(key, "Locked") ) {
locked = false;
}
}
EXIT:
xpkgkit_set_locked_state(locked);
return;
}
/* ========================================================================= *
* DBUS_IPC
* ========================================================================= */
/** Handle reply to xpkgkit_get_properties()
*
* @param pc pending call object
* @param aptr (not used)
*/
static void
xpkgkit_get_properties_cb(DBusPendingCall *pc, void *aptr)
{
(void)aptr;
mce_log(LL_DEBUG, "%s.%s %s",
DBUS_INTERFACE_PROPERTIES,
PROPERTIES_REQ_GET_ALL,
"reply");
DBusMessage *rsp = 0;
DBusError err = DBUS_ERROR_INIT;
if( !pc )
goto EXIT;
if( !(rsp = dbus_pending_call_steal_reply(pc)) )
goto EXIT;
if( dbus_set_error_from_message(&err, rsp) ) {
mce_log(LL_ERR, "%s: %s", err.name, err.message);
goto EXIT;
}
DBusMessageIter body;
dbus_message_iter_init(rsp, &body);
xpkgkit_parse_changed_properties(&body);
EXIT:
if( rsp ) dbus_message_unref(rsp);
dbus_error_free(&err);
}
/** Get list of PackageKit properties [async]
*
* Used for probing the initial state after PackageKit shows up
* on the SystemBus.
*/
static void
xpkgkit_get_properties(void)
{
const char *interface = PKGKIT_INTERFACE;
bool res = dbus_send(PKGKIT_SERVICE,
PKGKIT_OBJECT,
DBUS_INTERFACE_PROPERTIES,
PROPERTIES_REQ_GET_ALL,
xpkgkit_get_properties_cb,
DBUS_TYPE_STRING, &interface,
DBUS_TYPE_INVALID);
mce_log(LL_DEBUG, "%s.%s %s",
DBUS_INTERFACE_PROPERTIES,
PROPERTIES_REQ_GET_ALL,
res ? "sent ..." : "failed");
}
/** Handle reply to asynchronous PackageKit service name ownership query
*
* @param pc State data for asynchronous D-Bus method call
* @param aptr (not used)
*/
static void
xpkgkit_check_name_owner_cb(DBusPendingCall *pc, void *aptr)
{
(void)aptr;
DBusMessage *rsp = 0;
const char *owner = 0;
DBusError err = DBUS_ERROR_INIT;
if( !pc )
goto EXIT;
if( !(rsp = dbus_pending_call_steal_reply(pc)) )
goto EXIT;
if( dbus_set_error_from_message(&err, rsp) ||
!dbus_message_get_args(rsp, &err,
DBUS_TYPE_STRING, &owner,
DBUS_TYPE_INVALID) )
{
if( strcmp(err.name, DBUS_ERROR_NAME_HAS_NO_OWNER) ) {
mce_log(LL_WARN, "%s: %s", err.name, err.message);
goto EXIT;
}
}
xpkgkit_set_available_state(owner && *owner);
EXIT:
if( rsp ) dbus_message_unref(rsp);
dbus_error_free(&err);
}
/** Initiate asynchronous PackageKit service name ownership query
*
* Updates xpkgkit_is_available flag when reply message is received.
*/
static void
xpkgkit_check_name_owner(void)
{
const char *name = PKGKIT_SERVICE;
dbus_send(DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
DBUS_DAEMON_REQ_GET_NAME_OWNER,
xpkgkit_check_name_owner_cb,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
}
/* ========================================================================= *
* UPDATE_LOGGING
* ========================================================================= */
/** Name of the logging unit to start */
static const char xpkgkit_logging_unit_name[] = "osupdate-logging.service";
/** Stopping other units to fulfill dependencies is not ok */
static const char xpkgkit_logging_unit_start_mode[] = "fail";
/** Pending unit start request */
static DBusPendingCall *xpkgkit_logging_request_start_pc = 0;
/** Handle reply to logging unit start request from systemd
*/
static void
xpkgkit_logging_request_start_cb(DBusPendingCall *pc, void *aptr)
{
(void)aptr;
DBusMessage *rsp = 0;
DBusError err = DBUS_ERROR_INIT;
const char *job = 0;
if( !pc || pc != xpkgkit_logging_request_start_pc )
goto EXIT;
if( !(rsp = dbus_pending_call_steal_reply(pc)) ) {
mce_log(LL_ERR, "%s(%s): no reply",
SYSTEMD_MANAGER_START_UNIT,
xpkgkit_logging_unit_name);
goto EXIT;
}
if( dbus_set_error_from_message(&err, rsp) ) {
mce_log(LL_ERR, "%s(%s): %s: %s",
SYSTEMD_MANAGER_START_UNIT,
xpkgkit_logging_unit_name,
err.name, err.message);
goto EXIT;
}
if( !dbus_message_get_args(rsp, &err,
DBUS_TYPE_OBJECT_PATH, &job,
DBUS_TYPE_INVALID) ) {
mce_log(LL_ERR, "%s(%s): %s: %s",
SYSTEMD_MANAGER_START_UNIT,
xpkgkit_logging_unit_name,
err.name, err.message);
goto EXIT;
}
mce_log(LL_DEVEL, "%s(%s): job %s",
SYSTEMD_MANAGER_START_UNIT,
xpkgkit_logging_unit_name,
job ?: "n/a");
EXIT:
if( pc && pc == xpkgkit_logging_request_start_pc ) {
dbus_pending_call_unref(xpkgkit_logging_request_start_pc),
xpkgkit_logging_request_start_pc = 0;
}
if( rsp ) dbus_message_unref(rsp);
dbus_error_free(&err);
return;
}
/** Send logging unit start request to systemd
*/
static void
xpkgkit_logging_request_start(void)
{
if( xpkgkit_logging_request_start_pc )
goto EXIT;
const char *unit = xpkgkit_logging_unit_name;
const char *mode = xpkgkit_logging_unit_start_mode;
dbus_send_ex(SYSTEMD_SERVICE,
SYSTEMD_OBJECT,
SYSTEMD_MANAGER_INTERFACE,
SYSTEMD_MANAGER_START_UNIT,
xpkgkit_logging_request_start_cb,
0, 0,
&xpkgkit_logging_request_start_pc,
DBUS_TYPE_STRING, &unit,
DBUS_TYPE_STRING, &mode,
DBUS_TYPE_INVALID);
EXIT:
return;
}
/** Cancel pending logging unit start request
*/
static void xpkgkit_logging_cancel_start(void)
{
if( !xpkgkit_logging_request_start_pc )
goto EXIT;
dbus_pending_call_cancel(xpkgkit_logging_request_start_pc);
dbus_pending_call_unref(xpkgkit_logging_request_start_pc),
xpkgkit_logging_request_start_pc = 0;
EXIT:
return;
}
/* ========================================================================= *
* DATAPIPE_HANDLERS
* ========================================================================= */
/** Update mode is active; assume false */
static bool osupdate_running = false;
/** Change notifications for osupdate_running
*/
static void xpkgkit_datapipe_osupdate_running_cb(gconstpointer data)
{
bool prev = osupdate_running;
osupdate_running = GPOINTER_TO_INT(data);
if( osupdate_running == prev )
goto EXIT;
mce_log(LL_DEBUG, "osupdate_running = %d -> %d", prev, osupdate_running);
if( osupdate_running ) {
/* When update mode gets activated, we start a systemd
* service that will store journal to a persistent file
* until the next reboot. */
xpkgkit_logging_request_start();
}
EXIT:
return;
}
/** Array of datapipe handlers */
static datapipe_handler_t xpkgkit_datapipe_handlers[] =
{
// output triggers
{
.datapipe = &osupdate_running_pipe,
.output_cb = xpkgkit_datapipe_osupdate_running_cb,
},
// sentinel
{
.datapipe = 0,
}
};
static datapipe_bindings_t xpkgkit_datapipe_bindings =
{
.module = "xpkgkit",
.handlers = xpkgkit_datapipe_handlers,
};
/** Append triggers/filters to datapipes
*/
static void xpkgkit_datapipe_init(void)
{
mce_datapipe_init_bindings(&xpkgkit_datapipe_bindings);
}
/** Remove triggers/filters from datapipes
*/
static void xpkgkit_datapipe_quit(void)
{
mce_datapipe_quit_bindings(&xpkgkit_datapipe_bindings);
}
/* ========================================================================= *
* DBUS_HANDLERS
* ========================================================================= */
/** Handle D-Bus name owner changed signals for "org.freedesktop.PackageKit"
*/
static gboolean
xpkgkit_name_owner_changed_cb(DBusMessage *sig)
{
const gchar *name = 0;
const gchar *prev = 0;
const gchar *curr = 0;
DBusError err = DBUS_ERROR_INIT;
if( dbus_set_error_from_message(&err, sig) ) {
mce_log(LL_ERR, "%s: %s", err.name, err.message);
goto EXIT;
}
if( !dbus_message_get_args(sig, &err,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &prev,
DBUS_TYPE_STRING, &curr,
DBUS_TYPE_INVALID) ) {
mce_log(LL_ERR, "%s: %s", err.name, err.message);
goto EXIT;
}
if( !name || strcmp(name, PKGKIT_SERVICE) )
goto EXIT;
xpkgkit_set_available_state(curr && *curr);
EXIT:
dbus_error_free(&err);
return TRUE;
}
/** Handle "PropertiesChanged" signals from "org.freedesktop.PackageKit"
*/
static gboolean
xpkgkit_property_changed_cb(DBusMessage *sig)
{
const char *interface = 0;
DBusMessageIter body;
dbus_message_iter_init(sig, &body);
if( !mce_dbus_iter_get_string(&body, &interface) )
goto EXIT;
if( strcmp(interface, PKGKIT_INTERFACE) )
goto EXIT;
mce_log(LL_DEBUG, "properties changed");
xpkgkit_parse_changed_properties(&body);
xpkgkit_parse_dropped_properties(&body);
EXIT:
return TRUE;
}
/** Array of dbus message handlers */
static mce_dbus_handler_t handlers[] =
{
/* signals */
{
.interface = DBUS_INTERFACE_DBUS,
.name = DBUS_DAEMON_SIG_NAME_OWNER_CHANGED,
.rules = "arg0='"PKGKIT_SERVICE"'",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = xpkgkit_name_owner_changed_cb,
},
{
.interface = DBUS_INTERFACE_PROPERTIES,
.name = PROPERTIES_SIG_CHANGED,
.rules = "path='"PKGKIT_OBJECT"'",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = xpkgkit_property_changed_cb,
},
/* sentinel */
{
.interface = 0,
}
};
/* ========================================================================= *
* MODULE_LOAD_UNLOAD
* ========================================================================= */
/** Init function for the PackageKit module
*
* @param module (not used)
*
* @return NULL on success, a string with an error message on failure
*/
const gchar *g_module_check_init(GModule *module)
{
(void)module;
/* install datapipe handlers */
xpkgkit_datapipe_init();
/* install dbus message handlers */
mce_dbus_handler_register_array(handlers);
/* initiate async query to find out initial state of PackageKit */
xpkgkit_check_name_owner();
return NULL;
}
/** Exit function for the PackageKit module
*
* @param module (not used)
*/
void g_module_unload(GModule *module)
{
(void)module;
/* remove dbus message handlers */
mce_dbus_handler_unregister_array(handlers);
/* remove datapipe handlers */
xpkgkit_datapipe_quit();
/* cancel pending dbus requests */
xpkgkit_logging_cancel_start();
return;
}