diff options
| author | Jannis Pohlmann <jannis.pohlmann@codethink.co.uk> | 2012-07-31 17:45:10 +0100 |
|---|---|---|
| committer | Jannis Pohlmann <jannis.pohlmann@codethink.co.uk> | 2012-07-31 17:45:10 +0100 |
| commit | 44591b3d0baa3003bca0380fe3664fb4c210b79f (patch) | |
| tree | 82feb7384ce96bb9133b7954ce68dad1f4d81362 /node-startup-controller | |
| parent | 9d71065bfb59eb10fbd8ece605485d3864d08c08 (diff) | |
| download | node-startup-controller-44591b3d0baa3003bca0380fe3664fb4c210b79f.tar.gz | |
First step of renaming the Boot Manager to Node Startup Controller
This includes mostly files, directories and the package/tarball name.
The classes and D-Bus interfaces will be renamed in the next step.
Diffstat (limited to 'node-startup-controller')
23 files changed, 4237 insertions, 0 deletions
diff --git a/node-startup-controller/Makefile.am b/node-startup-controller/Makefile.am new file mode 100644 index 0000000..9d05213 --- /dev/null +++ b/node-startup-controller/Makefile.am @@ -0,0 +1,133 @@ +# vi:set ts=8 sw=8 noet ai nocindent: + +SUBDIRS = \ + busconf + +boot_managerdir = \ + $(libdir)/boot-manager-$(BOOT_MANAGER_VERSION_API) + +boot_manager_PROGRAMS = \ + boot-manager + +boot_manager_built_sources = \ + boot-manager-dbus.h \ + boot-manager-dbus.c + +systemd_manager_built_sources = \ + systemd-manager-dbus.h \ + systemd-manager-dbus.c + +systemd_unit_built_sources = \ + systemd-unit-dbus.h \ + systemd-unit-dbus.c + +boot_manager_SOURCES = \ + boot-manager-application.c \ + boot-manager-application.h \ + boot-manager-service.c \ + boot-manager-service.h \ + glib-extensions.c \ + glib-extensions.h \ + job-manager.c \ + job-manager.h \ + la-handler-service.c \ + la-handler-service.h \ + luc-starter.c \ + luc-starter.h \ + target-startup-monitor.c \ + target-startup-monitor.h \ + main.c \ + $(boot_manager_built_sources) \ + $(systemd_manager_built_sources) \ + $(systemd_unit_built_sources) + +boot_manager_CFLAGS = \ + -DLUC_PATH=\"$(sysconfdir)/boot-manager/last-user-context\" \ + -DG_LOG_DOMAIN=\"boot-manager\" \ + -I$(top_srcdir) \ + $(DLT_CFLAGS) \ + $(GIO_CFLAGS) \ + $(GIO_UNIX_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(PLATFORM_CFLAGS) \ + $(PLATFORM_CPPFLAGS) \ + $(SYSTEMD_DAEMON_CFLAGS) + +boot_manager_LDFLAGS = \ + -no-undefined \ + $(PLATFORM_LDFLAGS) + +boot_manager_DEPENDENCIES = \ + $(top_builddir)/common/libcommon.la + +boot_manager_LDADD = \ + $(DLT_LIBS) \ + $(GIO_LIBS) \ + $(GIO_UNIX_LIBS) \ + $(GLIB_LIBS) \ + $(SYSTEMD_DAEMON_LIBS) \ + $(top_builddir)/common/libcommon.la + +%.service: %.service.in + sed -e "s,\@libdir\@,$(libdir),g" \ + -e "s,\@BOOT_MANAGER_VERSION_API\@,$(BOOT_MANAGER_VERSION_API),g" < $< > $@ + +dbus_servicedir = $(datadir)/dbus-1/system-services + +dbus_service_in_files = \ + org.genivi.BootManager1.service.in + +dbus_service_DATA = $(dbus_service_in_files:.service.in=.service) + +systemd_servicedir = $(libdir)/systemd/system + +systemd_service_in_files = \ + boot-manager.service.in + +systemd_service_DATA = $(systemd_service_in_files:.service.in=.service) + +CLEANFILES = \ + $(dbus_service_DATA) \ + $(systemd_service_DATA) \ + boot-manager-dbus-doc-*.xml + +EXTRA_DIST = \ + boot-manager-dbus.xml \ + systemd-manager-dbus.xml \ + $(dbus_service_in_files) \ + $(systemd_service_in_files) + +DISTCLEANFILES = \ + $(boot_manager_built_sources) \ + $(systemd_manager_built_sources) \ + $(systemd_unit_built_sources) + +BUILT_SOURCES = \ + $(boot_manager_built_sources) \ + $(systemd_manager_built_sources) \ + $(systemd_unit_built_sources) + +$(boot_manager_built_sources): boot-manager-dbus.xml Makefile + $(AM_V_GEN) $(GDBUS_CODEGEN) \ + --interface-prefix org.genivi \ + --c-namespace "" \ + --generate-c-code boot-manager-dbus \ + --generate-docbook boot-manager-dbus-doc \ + --annotate org.genivi.BootManager1.BootManager org.gtk.GDBus.C.Name \ + Boot_Manager $< + +$(systemd_manager_built_sources): systemd-manager-dbus.xml Makefile + $(AM_V_GEN) $(GDBUS_CODEGEN) \ + --interface-prefix org.freedesktop.systemd1 \ + --c-namespace "" \ + --generate-c-code systemd-manager-dbus \ + --annotate org.freedesktop.systemd1.Manager org.gtk.GDBus.C.Name \ + SystemdManager $< + +$(systemd_unit_built_sources): systemd-unit-dbus.xml Makefile + $(AM_V_GEN) $(GDBUS_CODEGEN) \ + --interface-prefix org.freedesktop.systemd1 \ + --c-namespace "" \ + --generate-c-code systemd-unit-dbus \ + --annotate org.freedesktop.systemd1.Unit org.gtk.GDBus.C.Name \ + SystemdUnit $< diff --git a/node-startup-controller/boot-manager-application.c b/node-startup-controller/boot-manager-application.c new file mode 100644 index 0000000..5fb7436 --- /dev/null +++ b/node-startup-controller/boot-manager-application.c @@ -0,0 +1,577 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib-object.h> +#include <gio/gio.h> +#include <glib-unix.h> + +#include <systemd/sd-daemon.h> + +#include <dlt/dlt.h> + +#include <common/nsm-consumer-dbus.h> +#include <common/nsm-enum-types.h> +#include <common/shutdown-client.h> +#include <common/shutdown-consumer-dbus.h> +#include <common/watchdog-client.h> + +#include <node-startup-controller/boot-manager-application.h> +#include <node-startup-controller/boot-manager-service.h> +#include <node-startup-controller/job-manager.h> +#include <node-startup-controller/la-handler-service.h> +#include <node-startup-controller/luc-starter.h> + + + + +DLT_IMPORT_CONTEXT (boot_manager_context); + + + +/* property identifiers */ +enum +{ + PROP_0, + PROP_CONNECTION, + PROP_JOB_MANAGER, + PROP_BOOT_MANAGER_SERVICE, + PROP_LUC_STARTER, + PROP_LA_HANDLER, + PROP_MAIN_LOOP, +}; + + + +static void boot_manager_application_finalize (GObject *object); +static void boot_manager_application_constructed (GObject *object); +static void boot_manager_application_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static gboolean boot_manager_application_handle_lifecycle_request (ShutdownConsumer *interface, + GDBusMethodInvocation *invocation, + NSMShutdownType request, + guint request_id, + BootManagerApplication *application); +static void boot_manager_application_handle_register_finish (GObject *object, + GAsyncResult *res, + gpointer user_data); +static void boot_manager_application_handle_unregister_finish (GObject *object, + GAsyncResult *res, + gpointer user_data); +static void boot_manager_application_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void boot_manager_application_luc_groups_started (LUCStarter *starter, + BootManagerApplication *application); + + + +struct _BootManagerApplicationClass +{ + GObjectClass __parent__; +}; + +struct _BootManagerApplication +{ + GObject __parent__; + + /* the connection to D-Bus */ + GDBusConnection *connection; + + /* systemd watchdog client that repeatedly asks systemd to update + * the watchdog timestamp */ + WatchdogClient *watchdog_client; + + /* manager of unit start and stop operations */ + JobManager *job_manager; + + /* boot manager service */ + BootManagerService *boot_manager_service; + + /* LUC starter to restore the LUC */ + LUCStarter *luc_starter; + + /* Legacy App Handler to register apps with the Node State Manager */ + LAHandlerService *la_handler; + + /* the application's main loop */ + GMainLoop *main_loop; + + /* identifier for the registered bus name */ + guint bus_name_id; + + /* shutdown client for the boot manager itself */ + ShutdownClient *client; +}; + + + +G_DEFINE_TYPE (BootManagerApplication, boot_manager_application, G_TYPE_OBJECT); + + + +static void +boot_manager_application_class_init (BootManagerApplicationClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = boot_manager_application_finalize; + gobject_class->constructed = boot_manager_application_constructed; + gobject_class->get_property = boot_manager_application_get_property; + gobject_class->set_property = boot_manager_application_set_property; + + g_object_class_install_property (gobject_class, + PROP_CONNECTION, + g_param_spec_object ("connection", + "D-Bus Connection", + "The connection to D-Bus", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_JOB_MANAGER, + g_param_spec_object ("job-manager", + "Job Manager", + "The internal handler of Start()" + " and Stop() jobs", + TYPE_JOB_MANAGER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_LA_HANDLER, + g_param_spec_object ("la-handler", + "LA Handler", + "Legacy Application Handler", + LA_HANDLER_TYPE_SERVICE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_BOOT_MANAGER_SERVICE, + g_param_spec_object ("boot-manager-service", + "boot-manager-service", + "boot-manager-service", + BOOT_MANAGER_TYPE_SERVICE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_LUC_STARTER, + g_param_spec_object ("luc-starter", + "luc-starter", + "luc-starter", + TYPE_LUC_STARTER, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_MAIN_LOOP, + g_param_spec_boxed ("main-loop", + "main-loop", + "main-loop", + G_TYPE_MAIN_LOOP, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + + + +static void +boot_manager_application_init (BootManagerApplication *application) +{ + const gchar *watchdog_str; + guint64 watchdog_usec = 0; + guint watchdog_sec = 0; + gchar *message; + + /* read the WATCHDOG_USEC environment variable and parse it + * into an unsigned integer */ + watchdog_str = g_getenv ("WATCHDOG_USEC"); + if (watchdog_str != NULL) + watchdog_usec = g_ascii_strtoull (watchdog_str, NULL, 10); + + /* only create the watchdog client if a timeout was specified */ + if (watchdog_usec > 0) + { + /* halve the watchdog timeout because we need to notify systemd + * twice in every interval; also, convert it to seconds */ + watchdog_sec = (guint) ((watchdog_usec / 2) / 1000000); + + /* update systemd's watchdog timestamp in regular intervals */ + application->watchdog_client = watchdog_client_new (watchdog_sec); + + /* log information about the watchdog timeout using DLT */ + message = g_strdup_printf ("Updating systemd's watchdog timestamp every %d seconds", + watchdog_sec); + DLT_LOG (boot_manager_context, DLT_LOG_INFO, DLT_STRING (message)); + g_free (message); + } +} + + + +static void +boot_manager_application_finalize (GObject *object) +{ + BootManagerApplication *application = BOOT_MANAGER_APPLICATION (object); + ShutdownConsumer *consumer; + + /* disconnect from the shutdown consumer */ + consumer = shutdown_client_get_consumer (application->client); + g_signal_handlers_disconnect_matched (consumer, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, + application); + + /* release the shutdown client */ + g_object_unref (application->client); + + /* release the bus name */ + g_bus_unown_name (application->bus_name_id); + + /* release the D-Bus connection object */ + if (application->connection != NULL) + g_object_unref (application->connection); + + /* release the watchdog client */ + if (application->watchdog_client != NULL) + g_object_unref (application->watchdog_client); + + /* release the boot manager */ + g_object_unref (application->boot_manager_service); + + /* release the LUC starter */ + g_object_unref (application->luc_starter); + + /* release the legacy app handler */ + g_object_unref (application->la_handler); + + /* release the job manager */ + g_object_unref (application->job_manager); + + /* release the main loop */ + g_main_loop_unref (application->main_loop); + + (*G_OBJECT_CLASS (boot_manager_application_parent_class)->finalize) (object); +} + + + +static void +boot_manager_application_constructed (GObject *object) +{ + BootManagerApplication *application = BOOT_MANAGER_APPLICATION (object); + ShutdownConsumer *consumer; + NSMShutdownType shutdown_mode; + NSMConsumer *nsm_consumer; + GError *error = NULL; + gchar *bus_name = "org.genivi.BootManager1"; + gchar *log_text; + gchar *object_path; + gint timeout; + + /* instantiate the LUC starter */ + application->luc_starter = luc_starter_new (application->job_manager, + application->boot_manager_service); + + /* be notified when LUC groups have started so that we can hand + * control over to systemd again */ + g_signal_connect (application->luc_starter, "luc-groups-started", + G_CALLBACK (boot_manager_application_luc_groups_started), + application); + + /* restore the LUC if desired */ + luc_starter_start_groups (application->luc_starter); + + /* get a bus name on the given connection */ + application->bus_name_id = + g_bus_own_name_on_connection (application->connection, "org.genivi.BootManager1", + G_BUS_NAME_OWNER_FLAGS_NONE, NULL, NULL, NULL, NULL); + + /* create a shutdown client for the boot manager itself */ + object_path = "/org/genivi/BootManager1/ShutdownConsumer/0"; + shutdown_mode = NSM_SHUTDOWN_TYPE_NORMAL; + timeout = 1000; + application->client = shutdown_client_new (bus_name, object_path, shutdown_mode, + timeout); + + /* create a shutdown consumer and implement its LifecycleRequest method */ + consumer = shutdown_consumer_skeleton_new (); + shutdown_client_set_consumer (application->client, consumer); + g_signal_connect (consumer, "handle-lifecycle-request", + G_CALLBACK (boot_manager_application_handle_lifecycle_request), + application); + + /* export the shutdown consumer on the bus */ + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (consumer), + application->connection, object_path, &error)) + { + log_text = g_strdup_printf ("Failed to export shutdown consumer on the bus: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_clear_error (&error); + } + + /* register boot manager as a shutdown consumer */ + nsm_consumer = la_handler_service_get_nsm_consumer (application->la_handler); + nsm_consumer_call_register_shutdown_client (nsm_consumer, bus_name, object_path, + shutdown_mode, timeout, NULL, + boot_manager_application_handle_register_finish, + NULL); + + /* release the shutdown consumer */ + g_object_unref (consumer); +} + + + +static void +boot_manager_application_handle_register_finish (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + NSMConsumer *nsm_consumer = NSM_CONSUMER (object); + GError *error = NULL; + gchar *log_text; + gint error_code = NSM_ERROR_STATUS_OK; + + g_return_if_fail (IS_NSM_CONSUMER (nsm_consumer)); + g_return_if_fail (G_IS_ASYNC_RESULT (res)); + + /* finish registering boot manager as a shutdown client */ + if (!nsm_consumer_call_register_shutdown_client_finish (nsm_consumer, &error_code, res, + &error)) + { + log_text = g_strdup_printf ("Failed to register the boot manager as a shutdown " + "consumer: %s", error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + else if (error_code == NSM_ERROR_STATUS_OK) + { + log_text = g_strdup_printf ("The boot manager has registered as a shutdown " + "consumer"); + DLT_LOG (boot_manager_context, DLT_LOG_INFO, DLT_STRING (log_text)); + g_free (log_text); + } + else + { + log_text = g_strdup_printf ("Failed to register the boot manager as a shutdown " + "consumer: error status %d", error_code); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + } +} + + + +static void +boot_manager_application_handle_unregister_finish (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + BootManagerApplication *application = BOOT_MANAGER_APPLICATION (user_data); + NSMConsumer *nsm_consumer = NSM_CONSUMER (object); + GError *error = NULL; + gchar *log_text; + gint error_code = NSM_ERROR_STATUS_OK; + + g_return_if_fail (IS_NSM_CONSUMER (nsm_consumer)); + g_return_if_fail (BOOT_MANAGER_IS_APPLICATION (application)); + g_return_if_fail (G_IS_ASYNC_RESULT (res)); + + /* finish unregistering boot manager as a shutdown client */ + if (!nsm_consumer_call_un_register_shutdown_client_finish (nsm_consumer, &error_code, + res, &error)) + { + log_text = g_strdup_printf ("Failed to unregister the boot manager as a shutdown " + "consumer: %s", error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + else if (error_code == NSM_ERROR_STATUS_OK) + { + log_text = g_strdup_printf ("The boot manager has unregistered as a shutdown " + "consumer"); + DLT_LOG (boot_manager_context, DLT_LOG_INFO, DLT_STRING (log_text)); + g_free (log_text); + } + else + { + log_text = g_strdup_printf ("Failed to unregister the boot manager as a shutdown " + "consumer: error status %d", error_code); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + } + + /* quit the application */ + g_main_loop_quit (application->main_loop); +} + + + +static gboolean +boot_manager_application_handle_lifecycle_request (ShutdownConsumer *consumer, + GDBusMethodInvocation *invocation, + NSMShutdownType request, + guint request_id, + BootManagerApplication *application) +{ + NSMConsumer *nsm_consumer; + const gchar *bus_name; + const gchar *object_path; + gint shutdown_mode; + + g_return_val_if_fail (IS_SHUTDOWN_CONSUMER (consumer), FALSE); + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE); + g_return_val_if_fail (BOOT_MANAGER_IS_APPLICATION (application), FALSE); + + /* cancel the LUC startup */ + luc_starter_cancel (application->luc_starter); + + /* deregister the shutdown consumers */ + la_handler_service_deregister_consumers (application->la_handler); + + /* let the NSM know that we have handled the lifecycle request */ + shutdown_consumer_complete_lifecycle_request (consumer, invocation, + NSM_ERROR_STATUS_OK); + + /* deregister the boot manager as a shutdown client itself */ + nsm_consumer = la_handler_service_get_nsm_consumer (application->la_handler); + bus_name = shutdown_client_get_bus_name (application->client); + object_path = shutdown_client_get_object_path (application->client); + shutdown_mode = shutdown_client_get_shutdown_mode (application->client); + nsm_consumer_call_un_register_shutdown_client (nsm_consumer, bus_name, + object_path, shutdown_mode, NULL, + boot_manager_application_handle_unregister_finish, + application); + return TRUE; +} + + + +static void +boot_manager_application_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BootManagerApplication *application = BOOT_MANAGER_APPLICATION (object); + + switch (prop_id) + { + case PROP_CONNECTION: + g_value_set_object (value, application->connection); + break; + case PROP_JOB_MANAGER: + g_value_set_object (value, application->job_manager); + break; + case PROP_BOOT_MANAGER_SERVICE: + g_value_set_object (value, application->boot_manager_service); + case PROP_LA_HANDLER: + g_value_set_object (value, application->la_handler); + break; + case PROP_LUC_STARTER: + g_value_set_object (value, application->luc_starter); + break; + case PROP_MAIN_LOOP: + g_value_set_boxed (value, application->main_loop); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +boot_manager_application_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BootManagerApplication *application = BOOT_MANAGER_APPLICATION (object); + + switch (prop_id) + { + case PROP_CONNECTION: + application->connection = g_value_dup_object (value); + break; + case PROP_JOB_MANAGER: + application->job_manager = g_value_dup_object (value); + break; + case PROP_BOOT_MANAGER_SERVICE: + application->boot_manager_service = g_value_dup_object (value); + break; + case PROP_LA_HANDLER: + application->la_handler = g_value_dup_object (value); + break; + case PROP_MAIN_LOOP: + application->main_loop = g_main_loop_ref (g_value_get_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +boot_manager_application_luc_groups_started (LUCStarter *starter, + BootManagerApplication *application) +{ + g_return_if_fail (IS_LUC_STARTER (starter)); + g_return_if_fail (BOOT_MANAGER_IS_APPLICATION (application)); + + /* notify systemd that we have finished starting the LUC and + * that it can take over control to start unfocused.target, + * lazy.target etc. */ + sd_notify (0, "READY=1"); +} + + + +BootManagerApplication * +boot_manager_application_new (GMainLoop *main_loop, + GDBusConnection *connection, + JobManager *job_manager, + LAHandlerService *la_handler, + BootManagerService *boot_manager_service) +{ + g_return_val_if_fail (main_loop != NULL, NULL); + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (IS_JOB_MANAGER (job_manager), NULL); + g_return_val_if_fail (LA_HANDLER_IS_SERVICE (la_handler), NULL); + g_return_val_if_fail (BOOT_MANAGER_IS_SERVICE (boot_manager_service), NULL); + + return g_object_new (BOOT_MANAGER_TYPE_APPLICATION, + "connection", connection, + "boot-manager-service", boot_manager_service, + "job-manager", job_manager, + "la-handler", la_handler, + "main-loop", main_loop, + NULL); +} diff --git a/node-startup-controller/boot-manager-application.h b/node-startup-controller/boot-manager-application.h new file mode 100644 index 0000000..e7e5214 --- /dev/null +++ b/node-startup-controller/boot-manager-application.h @@ -0,0 +1,42 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef __BOOT_MANAGER_APPLICATION_H__ +#define __BOOT_MANAGER_APPLICATION_H__ + +#include <gio/gio.h> + +#include <node-startup-controller/boot-manager-service.h> +#include <node-startup-controller/job-manager.h> +#include <node-startup-controller/la-handler-service.h> + +G_BEGIN_DECLS + +#define BOOT_MANAGER_TYPE_APPLICATION (boot_manager_application_get_type ()) +#define BOOT_MANAGER_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BOOT_MANAGER_TYPE_APPLICATION, BootManagerApplication)) +#define BOOT_MANAGER_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BOOT_MANAGER_TYPE_APPLICATION, BootManagerApplicationClass)) +#define BOOT_MANAGER_IS_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BOOT_MANAGER_TYPE_APPLICATION)) +#define BOOT_MANAGER_IS_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BOOT_MANAGER_TYPE_APPLICATION)) +#define BOOT_MANAGER_APPLICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BOOT_MANAGER_TYPE_APPLICATION, BootManagerApplicationClass)) + +typedef struct _BootManagerApplicationClass BootManagerApplicationClass; +typedef struct _BootManagerApplication BootManagerApplication; + +GType boot_manager_application_get_type (void) G_GNUC_CONST; + +BootManagerApplication *boot_manager_application_new (GMainLoop *main_loop, + GDBusConnection *connection, + JobManager *job_manager, + LAHandlerService *la_handler, + BootManagerService *boot_manager_service) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; + +G_END_DECLS + +#endif /* !__BOOT_MANAGER_APPLICATION_H__ */ + diff --git a/node-startup-controller/boot-manager-dbus.xml b/node-startup-controller/boot-manager-dbus.xml new file mode 100644 index 0000000..c1e92a0 --- /dev/null +++ b/node-startup-controller/boot-manager-dbus.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/org/genivi/BootManager1/BootManager"> + <!-- + org.genivi.BootManager1.BootManager: + @short_description: Interface for managing the GENIVI LUC (Last User Context) + + The GENIVI Boot Manager remembers applications that were used in the last + session of a user. It is used in order to restore these applications on + the next start-up. + + The Boot Manager is a passive component in the sense that it does not + remember applications on its own; instead, applications need to register + and deregister themselves proactively. + + Applications can be registered for different LUC types, the standard ones + being "foreground", "background" and "audible". + --> + <interface name="org.genivi.BootManager1.BootManager"> + <!-- + BeginLUCRegistration: + + Initialises the LUC registration sequence at shutdown in order to register + the applications which are running in the LUC in this moment. + --> + <method name="BeginLUCRegistration"> + <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/> + </method> + + <!-- + RegisterWithLUC: + @apps: A dictionary that maps LUC types to arrays of application unit names. + + Registers one or more applications for certain LUC types. Applications may + be listed multiple times. For LUC types where only a single application may + be registered at a time, the last application in the corresponding list wins. + + An example for the apps parameter would be: + + { + 0 : [ "app1.unit" ], + 1 : [ "app2.unit", "app3.unit" ], + 2 : [ "app3.unit" ] + } + + where 0 = "foreground", 1 = background , 2 = "audible" + --> + <method name="RegisterWithLUC"> + <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/> + <arg name="apps" type="a{ias}" direction="in"/> + </method> + + <!-- + FinishLUCRegistration: + + Finishes the LUC registration sequence and atomically replaces the previous LUC. + --> + <method name="FinishLUCRegistration"> + <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/> + </method> + </interface> +</node> diff --git a/node-startup-controller/boot-manager-service.c b/node-startup-controller/boot-manager-service.c new file mode 100644 index 0000000..c775d44 --- /dev/null +++ b/node-startup-controller/boot-manager-service.c @@ -0,0 +1,545 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <glib-object.h> +#include <gio/gio.h> + +#include <dlt/dlt.h> + +#include <node-startup-controller/glib-extensions.h> +#include <node-startup-controller/boot-manager-dbus.h> +#include <node-startup-controller/boot-manager-service.h> + + + +DLT_IMPORT_CONTEXT (boot_manager_context); + + + +/* property identifiers */ +enum +{ + PROP_0, + PROP_CONNECTION, +}; + + + +static void boot_manager_service_finalize (GObject *object); +static void boot_manager_service_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void boot_manager_service_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static gboolean boot_manager_service_handle_begin_luc_registration (BootManager *interface, + GDBusMethodInvocation *invocation, + BootManagerService *service); +static gboolean boot_manager_service_handle_finish_luc_registration (BootManager *interface, + GDBusMethodInvocation *invocation, + BootManagerService *service); +static gboolean boot_manager_service_handle_register_with_luc (BootManager *interface, + GDBusMethodInvocation *invocation, + GVariant *apps, + BootManagerService *service); + + + +struct _BootManagerServiceClass +{ + GObjectClass __parent__; +}; + +struct _BootManagerService +{ + GObject __parent__; + + GDBusConnection *connection; + BootManager *interface; + + GVariant *current_user_context; + gboolean started_registration; +}; + + + +G_DEFINE_TYPE (BootManagerService, boot_manager_service, G_TYPE_OBJECT); + + + +static void +boot_manager_service_class_init (BootManagerServiceClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = boot_manager_service_finalize; + gobject_class->get_property = boot_manager_service_get_property; + gobject_class->set_property = boot_manager_service_set_property; + + g_object_class_install_property (gobject_class, + PROP_CONNECTION, + g_param_spec_object ("connection", + "connection", + "connection", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + + + +static void +boot_manager_service_init (BootManagerService *service) +{ + service->interface = boot_manager_skeleton_new (); + + /* initially, no registration is assumed to have been started */ + service->started_registration = FALSE; + + /* reset current user context */ + service->current_user_context = NULL; + + /* implement the RegisterWithLUC() handler */ + g_signal_connect (service->interface, "handle-register-with-luc", + G_CALLBACK (boot_manager_service_handle_register_with_luc), + service); + + /* implement the BeginLUCRegistration() handler */ + g_signal_connect (service->interface, "handle-begin-lucregistration", + G_CALLBACK (boot_manager_service_handle_begin_luc_registration), + service); + + /* implement the FinishLUCRegistration() handler */ + g_signal_connect (service->interface, "handle-finish-lucregistration", + G_CALLBACK (boot_manager_service_handle_finish_luc_registration), + service); +} + + + +static void +boot_manager_service_finalize (GObject *object) +{ + BootManagerService *service = BOOT_MANAGER_SERVICE (object); + + /* release the D-Bus connection object */ + g_object_unref (service->connection); + + /* release the interface skeleton */ + g_signal_handlers_disconnect_matched (service->interface, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, service); + g_object_unref (service->interface); + + /* release the current user context */ + if (service->current_user_context != NULL) + g_variant_unref (service->current_user_context); + + (*G_OBJECT_CLASS (boot_manager_service_parent_class)->finalize) (object); +} + + + +static void +boot_manager_service_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BootManagerService *service = BOOT_MANAGER_SERVICE (object); + + switch (prop_id) + { + case PROP_CONNECTION: + g_value_set_object (value, service->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +boot_manager_service_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BootManagerService *service = BOOT_MANAGER_SERVICE (object); + + switch (prop_id) + { + case PROP_CONNECTION: + service->connection = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static gboolean +boot_manager_service_handle_begin_luc_registration (BootManager *interface, + GDBusMethodInvocation *invocation, + BootManagerService *service) +{ + GVariantBuilder builder; + + g_return_val_if_fail (IS_BOOT_MANAGER (interface), FALSE); + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE); + g_return_val_if_fail (BOOT_MANAGER_IS_SERVICE (service), FALSE); + + /* mark the last user context registration as started */ + service->started_registration = TRUE; + + /* initialize the current user context */ + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ias}")); + service->current_user_context = g_variant_builder_end (&builder); + + /* notify the caller that we have handled the method call */ + g_dbus_method_invocation_return_value (invocation, NULL); + return TRUE; +} + + + +static gboolean +boot_manager_service_handle_finish_luc_registration (BootManager *interface, + GDBusMethodInvocation *invocation, + BootManagerService *service) +{ + GError *error = NULL; + gchar *log_text; + + g_return_val_if_fail (IS_BOOT_MANAGER (interface), FALSE); + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE); + g_return_val_if_fail (BOOT_MANAGER_IS_SERVICE (service), FALSE); + + /* check if last user context registration started */ + if (!service->started_registration) + { + log_text = g_strdup_printf ("Failed to finish LUC registration: " + "the registration sequence has not been started yet"); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + + /* notify the caller that we have handled the method call */ + g_dbus_method_invocation_return_value (invocation, NULL); + return TRUE; + } + + /* write the last user context in a file */ + boot_manager_service_write_luc (service, &error); + if (error != NULL) + { + log_text = g_strdup_printf ("Failed to finish LUC registration: %s", error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + + /* mark the last user context registration as finished */ + service->started_registration = FALSE; + + /* clear the current user context */ + g_variant_unref (service->current_user_context); + service->current_user_context = NULL; + + /* notify the caller that we have handled the register request */ + g_dbus_method_invocation_return_value (invocation, NULL); + return TRUE; +} + + + +static gboolean +boot_manager_service_handle_register_with_luc (BootManager *interface, + GDBusMethodInvocation *invocation, + GVariant *apps, + BootManagerService *service) +{ + GVariantBuilder dict_builder; + GHashTableIter hiter; + GVariantIter viter; + GHashTable *table; + GPtrArray *apps_array; + GVariant *current_context; + GVariant *current_apps; + GVariant *new_apps; + gpointer key; + GList *lp; + GList *luc_types; + gchar *app; + gchar *debug_text = NULL; + gchar *log_text = NULL; + guint n; + gint luc_type; + + g_return_val_if_fail (IS_BOOT_MANAGER (interface), FALSE); + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE); + g_return_val_if_fail (BOOT_MANAGER_IS_SERVICE (service), FALSE); + + /* check if last user context registration started */ + if (!service->started_registration) + { + log_text = g_strdup_printf ("Failed to register apps with the LUC: " + "The registration sequence has not been started yet"); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + + /* notify the caller that we have handled the register request */ + g_dbus_method_invocation_return_value (invocation, NULL); + return TRUE; + } + + /* create a hash table to merge the current context and the newly registered apps */ + table = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) g_ptr_array_unref); + + /* obtain the current content of the last user context */ + current_context = g_variant_ref (service->current_user_context); + + /* prepare app lists for all LUC types present in the current context */ + g_variant_iter_init (&viter, current_context); + while (g_variant_iter_loop (&viter, "{ias}", &luc_type, NULL)) + { + g_hash_table_insert (table, GINT_TO_POINTER (luc_type), + g_ptr_array_new_with_free_func (g_free)); + } + + /* add app lists for LUC types that are needed for the newly registered apps */ + g_variant_iter_init (&viter, apps); + while (g_variant_iter_loop (&viter, "{ias}", &luc_type, NULL)) + { + g_hash_table_insert (table, GINT_TO_POINTER (luc_type), + g_ptr_array_new_with_free_func (g_free)); + } + + /* we now have a hash table that has all LUC types involved in the + * current context and in the newly registered apps */ + + /* fill the app lists for each LUC type involved, make sure that newly registered + * apps are added at the end so that they are "prioritized" */ + g_hash_table_iter_init (&hiter, table); + while (g_hash_table_iter_next (&hiter, (gpointer) &key, (gpointer) &apps_array)) + { + /* get apps currently registered for the LUC type */ + current_apps = g_variant_lookup_value_with_int_key (current_context, + GPOINTER_TO_INT (key), + G_VARIANT_TYPE_STRING_ARRAY); + + /* get apps to be registered for the LUC type now */ + new_apps = g_variant_lookup_value_with_int_key (apps, + GPOINTER_TO_INT (key), + G_VARIANT_TYPE_STRING_ARRAY); + + /* add all currently registered apps unless they are to be registered now. + * this is because we want apps to be registered now to be moved to the end + * of the lists */ + for (n = 0; current_apps != NULL && n < g_variant_n_children (current_apps); n++) + { + g_variant_get_child (current_apps, n, "&s", &app); + if (!g_variant_string_array_has_string (new_apps, app)) + g_ptr_array_add (apps_array, g_strdup (app)); + } + + /* add all newly registered apps at the end now */ + for (n = 0; new_apps != NULL && n < g_variant_n_children (new_apps); n++) + { + g_variant_get_child (new_apps, n, "&s", &app); + g_ptr_array_add (apps_array, g_strdup (app)); + } + + /* release app lists for this LUC type */ + if (current_apps != NULL) + g_variant_unref (current_apps); + if (new_apps != NULL) + g_variant_unref (new_apps); + } + + /* construct a new dictionary variant for the new LUC */ + g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{ias}")); + + /* copy LUC types and corresponding apps over to the new context. + * make sure the order in which we add LUC types to the context + * dict is always the same. this is helpful for testing */ + luc_types = g_hash_table_get_keys (table); + luc_types = g_list_sort (luc_types, (GCompareFunc) g_int_pointer_compare); + for (lp = luc_types; lp != NULL; lp = lp->next) + { + /* get the apps list registered for this LUC type */ + apps_array = g_hash_table_lookup (table, lp->data); + + /* NULL-terminate the pointer so that we can treat it as a gchar ** */ + g_ptr_array_add (apps_array, NULL); + + /* add the LUC type and its apps to the new context */ + g_variant_builder_add (&dict_builder, "{i^as}", + GPOINTER_TO_INT (lp->data), apps_array->pdata); + } + + /* free the LUC types and our LUC type to apps mapping */ + g_list_free (luc_types); + g_hash_table_unref (table); + + /* free the last user context */ + g_variant_unref (service->current_user_context); + + /* apply the new last user context */ + service->current_user_context = g_variant_builder_end (&dict_builder); + + /* log the new last user context */ + debug_text = g_variant_print (service->current_user_context, TRUE); + log_text = g_strdup_printf ("The new context is: %s", debug_text); + DLT_LOG (boot_manager_context, DLT_LOG_DEBUG, DLT_STRING (log_text)); + g_free (debug_text); + g_free (log_text); + + /* release the current context */ + g_variant_unref (current_context); + + /* notify the caller that we have handled the register request */ + g_dbus_method_invocation_return_value (invocation, NULL); + + return TRUE; +} + + + +BootManagerService * +boot_manager_service_new (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + + return g_object_new (BOOT_MANAGER_TYPE_SERVICE, + "connection", connection, + NULL); +} + + + +gboolean +boot_manager_service_start_up (BootManagerService *service, + GError **error) +{ + g_return_val_if_fail (BOOT_MANAGER_IS_SERVICE (service), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* announce the org.genivi.BootManager1.BootManager service on the bus */ + return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (service->interface), + service->connection, + "/org/genivi/BootManager1/BootManager", + error); +} + + + +GVariant * +boot_manager_service_read_luc (BootManagerService *service, + GError **error) +{ + const gchar *luc_path; + GVariant *context; + GFile *luc_file; + char *data; + gsize data_len; + + g_return_val_if_fail (BOOT_MANAGER_IS_SERVICE (service), NULL); + g_return_val_if_fail ((error == NULL || *error == NULL), NULL); + + /* check which configuration file to use; the LUC_PATH environment variable + * has priority over the build-time LUC_PATH definition */ + luc_path = g_getenv ("LUC_PATH"); + if (luc_path == NULL) + luc_path = LUC_PATH; + + /* initialize the GFile */ + luc_file = g_file_new_for_path (luc_path); + + /* read the contents of the file */ + if (!g_file_load_contents (luc_file, NULL, &data, &data_len, NULL, error)) + { + g_object_unref (luc_file); + return NULL; + } + + /* store the contents of the file in a GVariant */ + context = g_variant_new_from_data (G_VARIANT_TYPE ("a{ias}"), data, data_len, + TRUE, g_free, data); + + g_object_unref (luc_file); + + return context; +} + + + +void +boot_manager_service_write_luc (BootManagerService *service, + GError **error) +{ + const gchar *luc_path; + GError *err = NULL; + GFile *luc_file; + GFile *luc_dir; + + g_return_if_fail (BOOT_MANAGER_IS_SERVICE (service)); + g_return_if_fail (error == NULL || *error == NULL); + + /* check which configuration file to use; the LUC_PATH environment variable + * has priority over the build-time LUC_PATH definition */ + luc_path = g_getenv ("LUC_PATH"); + if (luc_path == NULL) + luc_path = LUC_PATH; + + /* initialize the GFiles */ + luc_file = g_file_new_for_path (luc_path); + luc_dir = g_file_get_parent (luc_file); + + /* make sure the last user context's directory exists */ + if (!g_file_make_directory_with_parents (luc_dir, NULL, &err)) + { + if (err->domain == G_IO_ERROR && err->code == G_IO_ERROR_EXISTS) + { + /* clear the error for reuse */ + g_clear_error (&err); + } + else + { + /* let the caller know there was a problem */ + g_propagate_error (error, err); + g_object_unref (luc_file); + g_object_unref (luc_dir); + return; + } + } + + /* replace the contents with that of the file. g_file_replace_contents + * guarantees atomic overwriting and can make backups */ + g_file_replace_contents (luc_file, g_variant_get_data (service->current_user_context), + g_variant_get_size (service->current_user_context), NULL, + TRUE, G_FILE_CREATE_NONE, NULL, NULL, error); + + /* release the GFiles */ + g_object_unref (luc_file); + g_object_unref (luc_dir); +} diff --git a/node-startup-controller/boot-manager-service.h b/node-startup-controller/boot-manager-service.h new file mode 100644 index 0000000..7b4231c --- /dev/null +++ b/node-startup-controller/boot-manager-service.h @@ -0,0 +1,45 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef __BOOT_MANAGER_SERVICE_H__ +#define __BOOT_MANAGER_SERVICE_H__ + +#include <gio/gio.h> + +#include <node-startup-controller/systemd-manager-dbus.h> + +G_BEGIN_DECLS + +#define BOOT_MANAGER_TYPE_SERVICE (boot_manager_service_get_type ()) +#define BOOT_MANAGER_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BOOT_MANAGER_TYPE_SERVICE, BootManagerService)) +#define BOOT_MANAGER_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BOOT_MANAGER_TYPE_SERVICE, BootManagerServiceClass)) +#define BOOT_MANAGER_IS_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BOOT_MANAGER_TYPE_SERVICE)) +#define BOOT_MANAGER_IS_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BOOT_MANAGER_TYPE_SERVICE)) +#define BOOT_MANAGER_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BOOT_MANAGER_TYPE_SERVICE, BootManagerServiceClass)) + +typedef struct _BootManagerServiceClass BootManagerServiceClass; +typedef struct _BootManagerService BootManagerService; + + + +GType boot_manager_service_get_type (void) G_GNUC_CONST; + +BootManagerService *boot_manager_service_new (GDBusConnection *connection) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; +gboolean boot_manager_service_start_up (BootManagerService *service, + GError **error); +GVariant *boot_manager_service_read_luc (BootManagerService *service, + GError **error); +void boot_manager_service_write_luc (BootManagerService *service, + GError **error); + + +G_END_DECLS + +#endif /* !__BOOT_MANAGER_SERVICE_H__ */ + diff --git a/node-startup-controller/boot-manager.service.in b/node-startup-controller/boot-manager.service.in new file mode 100644 index 0000000..e255295 --- /dev/null +++ b/node-startup-controller/boot-manager.service.in @@ -0,0 +1,5 @@ +[Service] +Type = dbus +BusName = org.genivi.BootManager1 +ExecStart = @libdir@/boot-manager-@BOOT_MANAGER_VERSION_API@/boot-manager +WatchdogSec = 5 diff --git a/node-startup-controller/busconf/Makefile.am b/node-startup-controller/busconf/Makefile.am new file mode 100644 index 0000000..6a03e55 --- /dev/null +++ b/node-startup-controller/busconf/Makefile.am @@ -0,0 +1,9 @@ +# vi:set ts=8 sw=8 noet ai nocindent: + +boot_manager_confdir = $(sysconfdir)/dbus-1/system.d + +boot_manager_conf_DATA = \ + org.genivi.BootManager1.conf + +EXTRA_DIST = \ + org.genivi.BootManager1.conf diff --git a/node-startup-controller/busconf/org.genivi.BootManager1.conf b/node-startup-controller/busconf/org.genivi.BootManager1.conf new file mode 100644 index 0000000..b8c422f --- /dev/null +++ b/node-startup-controller/busconf/org.genivi.BootManager1.conf @@ -0,0 +1,9 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <policy user="root"> + <allow own="org.genivi.BootManager1"/> + <allow send_destination="org.genivi.BootManager1" /> + <allow receive_sender="org.genivi.BootManager1" /> + </policy> +</busconfig> diff --git a/node-startup-controller/glib-extensions.c b/node-startup-controller/glib-extensions.c new file mode 100644 index 0000000..d3f161f --- /dev/null +++ b/node-startup-controller/glib-extensions.c @@ -0,0 +1,100 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> + +#include <node-startup-controller/glib-extensions.h> + + + +GVariant * +g_variant_lookup_value_with_int_key (GVariant *dictionary, + const gint key, + const GVariantType *expected_type) +{ + GVariantIter iter; + GVariant *entry; + GVariant *entry_key; + GVariant *tmp; + GVariant *value; + gboolean matches; + + g_return_val_if_fail (g_variant_is_of_type (dictionary, G_VARIANT_TYPE ("a{i*}")), + NULL); + + g_variant_iter_init (&iter, dictionary); + + while ((entry = g_variant_iter_next_value (&iter))) + { + entry_key = g_variant_get_child_value (entry, 0); + matches = (g_variant_get_int32(entry_key) == key); + g_variant_unref (entry_key); + + if (matches) + break; + + g_variant_unref (entry); + } + + if (entry == NULL) + return NULL; + + value = g_variant_get_child_value (entry, 1); + g_variant_unref (entry); + + if (g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT)) + { + tmp = g_variant_get_variant (value); + g_variant_unref (value); + + if (expected_type && !g_variant_is_of_type (tmp, expected_type)) + { + g_variant_unref (tmp); + tmp = NULL; + } + + value = tmp; + } + + g_return_val_if_fail (expected_type == NULL || value == NULL || + g_variant_is_of_type (value, expected_type), NULL); + + return value; +} + + + +gboolean +g_variant_string_array_has_string (GVariant *array, + const gchar *str) +{ + gboolean found = FALSE; + gchar *current_str; + guint n; + + for (n = 0; array != NULL && !found && n < g_variant_n_children (array); n++) + { + g_variant_get_child (array, n, "&s", ¤t_str); + if (g_strcmp0 (str, current_str) == 0) + found = TRUE; + } + + return found; +} + + +gint +g_int_pointer_compare (gconstpointer a, gconstpointer b) +{ + return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b); +} diff --git a/node-startup-controller/glib-extensions.h b/node-startup-controller/glib-extensions.h new file mode 100644 index 0000000..8602841 --- /dev/null +++ b/node-startup-controller/glib-extensions.h @@ -0,0 +1,27 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef __GLIB_EXTENSIONS_H__ +#define __GLIB_EXTENSIONS_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +GVariant *g_variant_lookup_value_with_int_key (GVariant *dictionary, + const gint key, + const GVariantType *expected_type); +gboolean g_variant_string_array_has_string (GVariant *array, + const gchar *str); +gint g_int_pointer_compare (gconstpointer a, + gconstpointer b); + +G_END_DECLS + +#endif /* !__GLIB_EXTENSION_H__ */ diff --git a/node-startup-controller/job-manager.c b/node-startup-controller/job-manager.c new file mode 100644 index 0000000..a3f7318 --- /dev/null +++ b/node-startup-controller/job-manager.c @@ -0,0 +1,471 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib-object.h> +#include <gio/gio.h> + +#include <node-startup-controller/job-manager.h> +#include <node-startup-controller/systemd-manager-dbus.h> + + + +typedef struct _JobManagerJob JobManagerJob; + + + +/* property identifiers */ +enum +{ + PROP_0, + PROP_CONNECTION, + PROP_SYSTEMD_MANAGER, +}; + + + +static void job_manager_constructed (GObject *object); +static void job_manager_finalize (GObject *object); +static void job_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void job_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void job_manager_start_unit_reply (GObject *object, + GAsyncResult *result, + gpointer user_data); +static void job_manager_stop_unit_reply (GObject *object, + GAsyncResult *result, + gpointer user_data); +static void job_manager_job_removed (SystemdManager *systemd_manager, + guint id, + const gchar *job_name, + const gchar *unit, + const gchar *result, + JobManager *job_manager); +static JobManagerJob *job_manager_job_new (JobManager *manager, + const gchar *unit, + GCancellable *cancellable, + JobManagerCallback callback, + gpointer user_data); +static void job_manager_job_unref (JobManagerJob *job); +static void job_manager_remember_job (JobManager *manager, + const gchar *job_name, + JobManagerJob *job); +static void job_manager_forget_job (JobManager *manager, + const gchar *job_name); + + + +struct _JobManagerClass +{ + GObjectClass __parent__; +}; + +struct _JobManager +{ + GObject __parent__; + + GDBusConnection *connection; + SystemdManager *systemd_manager; + + GHashTable *jobs; +}; + +struct _JobManagerJob +{ + JobManager *manager; + gchar *unit; + GCancellable *cancellable; + JobManagerCallback callback; + gpointer user_data; +}; + + + +G_DEFINE_TYPE (JobManager, job_manager, G_TYPE_OBJECT); + + + +static void +job_manager_class_init (JobManagerClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = job_manager_finalize; + gobject_class->constructed = job_manager_constructed; + gobject_class->get_property = job_manager_get_property; + gobject_class->set_property = job_manager_set_property; + + g_object_class_install_property (gobject_class, + PROP_CONNECTION, + g_param_spec_object ("connection", + "connection", + "connection", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_SYSTEMD_MANAGER, + g_param_spec_object ("systemd-manager", + "systemd-manager", + "systemd-manager", + TYPE_SYSTEMD_MANAGER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + + + +static void +job_manager_init (JobManager *manager) +{ + /* create a mapping of systemd job names to job objects; we will use this + * to remember jobs that we started */ + manager->jobs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) job_manager_job_unref); + +} + + + +static void +job_manager_finalize (GObject *object) +{ + JobManager *manager = JOB_MANAGER (object); + + /* release all the jobs we have remembered */ + g_hash_table_unref (manager->jobs); + + /* release the D-Bus connection */ + g_object_unref (manager->connection); + + /* release the systemd manager */ + g_signal_handlers_disconnect_matched (manager->systemd_manager, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, manager); + g_object_unref (manager->systemd_manager); + + /* chain up to finalize parent class */ + (*G_OBJECT_CLASS (job_manager_parent_class)->finalize) (object); +} + + + +static void +job_manager_constructed (GObject *object) +{ + JobManager *manager = JOB_MANAGER (object); + + /* connect to systemd's "JobRemoved" signal so that we are notified + * whenever a job is finished */ + g_signal_connect (manager->systemd_manager, "job-removed", + G_CALLBACK (job_manager_job_removed), manager); +} + + + +static void +job_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + JobManager *manager = JOB_MANAGER (object); + + switch (prop_id) + { + case PROP_CONNECTION: + g_value_set_object (value, manager->connection); + break; + case PROP_SYSTEMD_MANAGER: + g_value_set_object (value, manager->systemd_manager); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +job_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + JobManager *manager = JOB_MANAGER (object); + + switch (prop_id) + { + case PROP_CONNECTION: + manager->connection = g_value_dup_object (value); + break; + case PROP_SYSTEMD_MANAGER: + manager->systemd_manager = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +job_manager_start_unit_reply (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + JobManagerJob *job = user_data; + GError *error = NULL; + gchar *job_name = NULL; + + g_return_if_fail (IS_SYSTEMD_MANAGER (object)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (user_data != NULL); + + /* finish the start unit call */ + if (!systemd_manager_call_start_unit_finish (job->manager->systemd_manager, + &job_name, result, &error)) + { + /* there was an error. notify the caller */ + job->callback (job->manager, job->unit, "failed", error, job->user_data); + g_error_free (error); + g_free (job_name); + + /* finish the job immediately */ + job_manager_job_unref (job); + } + else + { + /* remember the job so that we can finish it in the "job-removed" signal handler. + * the service takes ownership of the job so we don't need to unref it here */ + job_manager_remember_job (job->manager, job_name, job); + } +} + + + +static void +job_manager_stop_unit_reply (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + JobManagerJob *job = user_data; + GError *error = NULL; + gchar *job_name = NULL; + + g_return_if_fail (IS_SYSTEMD_MANAGER (object)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (user_data != NULL); + + /* finish the stop unit call */ + if (!systemd_manager_call_stop_unit_finish (job->manager->systemd_manager, + &job_name, result, &error)) + { + /* there was an error. notify the caller */ + job->callback (job->manager, job->unit, "failed", error, job->user_data); + g_error_free (error); + g_free (job_name); + + /* finish the job immediately */ + job_manager_job_unref (job); + } + else + { + /* remember the job so that we can finish it in the "job-removed" signal handler. + * the service takes ownership of the job so we don't need to unref it here */ + job_manager_remember_job (job->manager, job_name, job); + } +} + + + +static void +job_manager_job_removed (SystemdManager *systemd_manager, + guint id, + const gchar *job_name, + const gchar *unit, + const gchar *result, + JobManager *job_manager) +{ + JobManagerJob *job; + + g_return_if_fail (IS_SYSTEMD_MANAGER (systemd_manager)); + g_return_if_fail (job_name != NULL && *job_name != '\0'); + g_return_if_fail (result != NULL && *result != '\0'); + g_return_if_fail (unit != NULL && *unit != '\0'); + g_return_if_fail (IS_JOB_MANAGER (job_manager)); + + /* look up the remembered job for this job name */ + job = g_hash_table_lookup (job_manager->jobs, job_name); + + /* if no job is found, ignore this job-removed signal */ + if (job == NULL) + return; + + /* finish the job by notifying the caller */ + job->callback (job_manager, job->unit, result, NULL, job->user_data); + + /* forget about this job */ + job_manager_forget_job (job_manager, job_name); +} + + + +static JobManagerJob * +job_manager_job_new (JobManager *manager, + const gchar *unit, + GCancellable *cancellable, + JobManagerCallback callback, + gpointer user_data) +{ + JobManagerJob *job; + + g_return_val_if_fail (IS_JOB_MANAGER (manager), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + + /* allocate a new job struct */ + job = g_slice_new0 (JobManagerJob); + job->manager = g_object_ref (manager); + job->unit = g_strdup(unit); + if (cancellable != NULL) + job->cancellable = g_object_ref (cancellable); + job->callback = callback; + job->user_data = user_data; + + return job; +} + + +static void +job_manager_job_unref (JobManagerJob *job) +{ + if (job == NULL) + return; + + /* release all memory and references held by job */ + if (job->cancellable != NULL) + g_object_unref (job->cancellable); + g_free (job->unit); + g_object_unref (job->manager); + g_slice_free (JobManagerJob, job); +} + + + +static void +job_manager_remember_job (JobManager *manager, + const char *job_name, + JobManagerJob *job) +{ + JobManagerJob *existing_job; + + g_return_if_fail (IS_JOB_MANAGER (manager)); + g_return_if_fail (job_name != NULL && *job_name != '\0'); + g_return_if_fail (job != NULL); + + /* if the job is already being remembered, there is a programming error that should be + * notified */ + existing_job = g_hash_table_lookup (manager->jobs, job_name); + if (existing_job != NULL) + { + g_critical ("Trying to remember the same job twice."); + return; + } + /* associate the job name with the job */ + g_hash_table_insert (manager->jobs, g_strdup (job_name), job); +} + + + +static void +job_manager_forget_job (JobManager *manager, + const gchar *job_name) +{ + g_return_if_fail (IS_JOB_MANAGER (manager)); + g_return_if_fail (job_name != NULL && *job_name != '\0'); + + g_hash_table_remove (manager->jobs, job_name); +} + + + +JobManager * +job_manager_new (GDBusConnection *connection, + SystemdManager *systemd_manager) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (IS_SYSTEMD_MANAGER (systemd_manager), NULL); + + return g_object_new (TYPE_JOB_MANAGER, + "connection", connection, + "systemd-manager", systemd_manager, + NULL); +} + + + +void +job_manager_start (JobManager *manager, + const gchar *unit, + GCancellable *cancellable, + JobManagerCallback callback, + gpointer user_data) +{ + JobManagerJob *job; + + g_return_if_fail (IS_JOB_MANAGER (manager)); + g_return_if_fail (unit != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (callback != NULL); + + /* create a new job object */ + job = job_manager_job_new (manager, unit, cancellable, callback, user_data); + + /* ask systemd to start the unit asynchronously */ + systemd_manager_call_start_unit (manager->systemd_manager, unit, "fail", cancellable, + job_manager_start_unit_reply, job); +} + + + +void +job_manager_stop (JobManager *manager, + const gchar *unit, + GCancellable *cancellable, + JobManagerCallback callback, + gpointer user_data) +{ + JobManagerJob *job; + + g_return_if_fail (IS_JOB_MANAGER (manager)); + g_return_if_fail (unit != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (callback != NULL); + + /* create a new job object */ + job = job_manager_job_new (manager, unit, cancellable, callback, user_data); + + /* ask systemd to stop the unit asynchronously */ + systemd_manager_call_stop_unit (manager->systemd_manager, unit, "fail", cancellable, + job_manager_stop_unit_reply, job); +} diff --git a/node-startup-controller/job-manager.h b/node-startup-controller/job-manager.h new file mode 100644 index 0000000..9eed6b2 --- /dev/null +++ b/node-startup-controller/job-manager.h @@ -0,0 +1,49 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef __JOB_MANAGER_H__ +#define __JOB_MANAGER_H__ + +#include <node-startup-controller/systemd-manager-dbus.h> + +G_BEGIN_DECLS + +#define TYPE_JOB_MANAGER (job_manager_get_type()) +#define JOB_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_JOB_MANAGER, JobManager)) +#define JOB_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_JOB_MANAGER, JobManagerClass)) +#define IS_JOB_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_JOB_MANAGER)) +#define IS_JOB_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass, TYPE_JOB_MANAGER)) +#define JOB_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj, TYPE_JOB_MANAGER, JobManagerClass)) + +typedef struct _JobManagerClass JobManagerClass; +typedef struct _JobManager JobManager; + +typedef void (*JobManagerCallback) (JobManager *manager, + const gchar *unit, + const gchar *result, + GError *error, + gpointer user_data); + +GType job_manager_get_type (void) G_GNUC_CONST; +JobManager *job_manager_new (GDBusConnection *connection, + SystemdManager *systemd_manager) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; +void job_manager_start (JobManager *manager, + const gchar *unit, + GCancellable *cancellable, + JobManagerCallback callback, + gpointer user_data); +void job_manager_stop (JobManager *manager, + const gchar *unit, + GCancellable *cancellable, + JobManagerCallback callback, + gpointer user_data); + +G_END_DECLS + +#endif /* !__JOB_MANAGER_H__ */ diff --git a/node-startup-controller/la-handler-service.c b/node-startup-controller/la-handler-service.c new file mode 100644 index 0000000..edcd219 --- /dev/null +++ b/node-startup-controller/la-handler-service.c @@ -0,0 +1,683 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib-object.h> +#include <gio/gio.h> + +#include <dlt/dlt.h> + +#include <common/la-handler-dbus.h> +#include <common/nsm-consumer-dbus.h> +#include <common/nsm-enum-types.h> +#include <common/shutdown-client.h> +#include <common/shutdown-consumer-dbus.h> + +#include <node-startup-controller/job-manager.h> +#include <node-startup-controller/la-handler-service.h> + + + +DLT_IMPORT_CONTEXT (la_handler_context); + + + +/* property identifiers */ +enum +{ + PROP_0, + PROP_CONNECTION, + PROP_JOB_MANAGER, +}; + + + +typedef struct _LAHandlerServiceData LAHandlerServiceData; + + + +static void la_handler_service_constructed (GObject *object); +static void la_handler_service_finalize (GObject *object); +static void la_handler_service_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void la_handler_service_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static gboolean la_handler_service_handle_register (LAHandler *interface, + GDBusMethodInvocation *invocation, + const gchar *unit, + NSMShutdownType mode, + guint timeout, + LAHandlerService *service); +static void la_handler_service_handle_register_finish (GObject *object, + GAsyncResult *res, + gpointer user_data); +static gboolean la_handler_service_handle_consumer_lifecycle_request (ShutdownConsumer *consumer, + GDBusMethodInvocation *invocation, + guint request, + guint request_id, + ShutdownClient *client); +static void la_handler_service_handle_consumer_lifecycle_request_finish (JobManager *manager, + const gchar *unit, + const gchar *result, + GError *error, + gpointer user_data); +static LAHandlerServiceData *la_handler_service_data_new (LAHandlerService *service, + GDBusMethodInvocation *invocation, + guint request_id); +static void la_handler_service_data_unref (LAHandlerServiceData *data); + + + +struct _LAHandlerServiceClass +{ + GObjectClass __parent__; +}; + +struct _LAHandlerService +{ + GObject __parent__; + + GDBusConnection *connection; + LAHandler *interface; + JobManager *job_manager; + + /* Associations of shutdown clients and their units */ + GHashTable *units_to_clients; + GHashTable *clients_to_units; + + const gchar *prefix; + guint index; + guint bus_name_id; + + /* connection to the NSM consumer interface */ + NSMConsumer *nsm_consumer; +}; + +struct _LAHandlerServiceData +{ + GDBusMethodInvocation *invocation; + LAHandlerService *service; + guint request_id; +}; + + + +G_DEFINE_TYPE (LAHandlerService, la_handler_service, G_TYPE_OBJECT); + + + +static void +la_handler_service_class_init (LAHandlerServiceClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->constructed = la_handler_service_constructed; + gobject_class->finalize = la_handler_service_finalize; + gobject_class->get_property = la_handler_service_get_property; + gobject_class->set_property = la_handler_service_set_property; + + g_object_class_install_property (gobject_class, + PROP_CONNECTION, + g_param_spec_object ("connection", + "connection", + "connection", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, + PROP_JOB_MANAGER, + g_param_spec_object ("job-manager", + "Job Manager", + "The internal handler of Start()" + " and Stop() jobs", + TYPE_JOB_MANAGER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + + + +static void +la_handler_service_constructed (GObject *object) +{ + LAHandlerService *service = LA_HANDLER_SERVICE (object); + GError *error = NULL; + gchar *log_text; + + /* connect to the node state manager */ + service->nsm_consumer = + nsm_consumer_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, + "com.contiautomotive.NodeStateManager", + "/com/contiautomotive/NodeStateManager/Consumer", + NULL, &error); + if (error != NULL) + { + log_text = g_strdup_printf ("Error occurred connecting to NSM Consumer: %s", + error->message); + DLT_LOG (la_handler_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } +} + + + +static void +la_handler_service_init (LAHandlerService *service) +{ + service->interface = la_handler_skeleton_new (); + + /* the number that follows the prefix in the shutdown client's object path, + * making every shutdown client unique */ + service->index = 1; + + /* the string that precedes the index in the shutdown client's object path */ + service->prefix = "/org/genivi/BootManager1/ShutdownConsumer"; + + /* initialize the association of shutdown client to units */ + service->units_to_clients = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + service->clients_to_units = g_hash_table_new_full (g_direct_hash, g_direct_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) g_free); + + /* implement the Register() handler */ + g_signal_connect (service->interface, "handle-register", + G_CALLBACK (la_handler_service_handle_register), + service); +} + + + +static void +la_handler_service_finalize (GObject *object) +{ + LAHandlerService *service = LA_HANDLER_SERVICE (object); + + /* release the bus name */ + g_bus_unown_name (service->bus_name_id); + + /* release the NSM consumer service object, if there is one */ + if (service->nsm_consumer != NULL) + g_object_unref (service->nsm_consumer); + + /* release the interface skeleton */ + g_signal_handlers_disconnect_matched (service->interface, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, service); + g_object_unref (service->interface); + + /* release the job manager skeleton */ + g_object_unref (service->job_manager); + + /* release the D-Bus connection object */ + if (service->connection != NULL) + g_object_unref (service->connection); + + /* release the shutdown clients */ + g_hash_table_unref (service->units_to_clients); + g_hash_table_unref (service->clients_to_units); + + (*G_OBJECT_CLASS (la_handler_service_parent_class)->finalize) (object); +} + + + +static void +la_handler_service_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + LAHandlerService *service = LA_HANDLER_SERVICE (object); + + switch (prop_id) + { + case PROP_CONNECTION: + g_value_set_object (value, service->connection); + break; + case PROP_JOB_MANAGER: + g_value_set_object (value, service->job_manager); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +la_handler_service_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + LAHandlerService *service = LA_HANDLER_SERVICE (object); + + switch (prop_id) + { + case PROP_CONNECTION: + service->connection = g_value_dup_object (value); + break; + case PROP_JOB_MANAGER: + service->job_manager = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static gboolean +la_handler_service_handle_register (LAHandler *interface, + GDBusMethodInvocation *invocation, + const gchar *unit, + NSMShutdownType shutdown_mode, + guint timeout, + LAHandlerService *service) +{ + ShutdownConsumer *consumer; + ShutdownClient *client; + GError *error = NULL; + const gchar *existing_bus_name; + const gchar *existing_object_path; + gchar *bus_name; + gchar *log_text; + gchar *object_path; + + g_return_val_if_fail (IS_LA_HANDLER (interface), FALSE); + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE); + g_return_val_if_fail (unit != NULL && *unit != '\0', FALSE); + g_return_val_if_fail (LA_HANDLER_IS_SERVICE (service), FALSE); + + /* find out if we have a shutdown client for this unit already */ + client = g_hash_table_lookup (service->units_to_clients, unit); + if (client != NULL) + { + /* there already is a shutdown client for the unit, so simply + * re-register its client with the new shutdown mode and timeout */ + + /* extract information from the client */ + consumer = shutdown_client_get_consumer (client); + existing_bus_name = shutdown_client_get_bus_name (client); + existing_object_path = shutdown_client_get_object_path (client); + + /* temporarily store a reference to the legacy app handler service object + * in the invocation object */ + g_object_set_data_full (G_OBJECT (invocation), "la-handler-service", + g_object_ref (service), (GDestroyNotify) g_object_unref); + + /* re-register the shutdown consumer with the NSM Consumer */ + nsm_consumer_call_register_shutdown_client (service->nsm_consumer, + existing_bus_name, existing_object_path, + shutdown_mode, timeout, NULL, + la_handler_service_handle_register_finish, + invocation); + } + else + { + /* create a new shutdown client and consumer for the unit */ + bus_name = "org.genivi.BootManager1"; + object_path = g_strdup_printf ("%s/%u", service->prefix, service->index); + client = shutdown_client_new (bus_name, object_path, shutdown_mode, timeout); + consumer = shutdown_consumer_skeleton_new (); + shutdown_client_set_consumer (client, consumer); + + /* remember the legacy app handler service object in shutdown client */ + g_object_set_data_full (G_OBJECT (client), "la-handler-service", + g_object_ref (service), (GDestroyNotify) g_object_unref); + + /* implement the LifecycleRequest method of the shutdown consumer */ + g_signal_connect (consumer, "handle-lifecycle-request", + G_CALLBACK (la_handler_service_handle_consumer_lifecycle_request), + client); + + /* associate the shutdown client with the unit name */ + g_hash_table_insert (service->units_to_clients, g_strdup (unit), + g_object_ref (client)); + g_hash_table_insert (service->clients_to_units, g_object_ref (client), + g_strdup (unit)); + + /* export the shutdown consumer on the bus */ + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (consumer), + service->connection, object_path, &error); + if (error != NULL) + { + log_text = g_strdup_printf ("Failed to export shutdown consumer on the bus: %s", + error->message); + DLT_LOG (la_handler_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + + /* temporarily store a reference to the legacy app handler service object + * in the invocation object */ + g_object_set_data_full (G_OBJECT (invocation), "la-handler-service", + g_object_ref (service), (GDestroyNotify) g_object_unref); + + /* register the shutdown consumer with the NSM Consumer */ + nsm_consumer_call_register_shutdown_client (service->nsm_consumer, + bus_name, object_path, + shutdown_mode, timeout, NULL, + la_handler_service_handle_register_finish, + invocation); + + /* free strings and release the shutdown consumer */ + g_free (object_path); + g_object_unref (consumer); + + /* increment the counter for our shutdown consumer object paths */ + service->index++; + } + + return TRUE; +} + + + +static void +la_handler_service_handle_register_finish (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data); + LAHandlerService *service; + NSMConsumer *nsm_consumer = NSM_CONSUMER (object); + GError *error = NULL; + gchar *log_text; + gint error_code; + + g_return_if_fail (IS_NSM_CONSUMER (nsm_consumer)); + g_return_if_fail (G_IS_ASYNC_RESULT (res)); + g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation)); + + /* finish registering the shutdown client */ + nsm_consumer_call_register_shutdown_client_finish (nsm_consumer, &error_code, res, + &error); + if (error != NULL) + { + log_text = g_strdup_printf ("Failed to register a shutdown consumer: %s", + error->message); + DLT_LOG (la_handler_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + + /* retrieve the LAHandlerService from the invocation object */ + service = g_object_get_data (G_OBJECT (invocation), "la-handler-service"); + + /* notify the caller that we have handled the registration request */ + la_handler_complete_register (service->interface, invocation); +} + + + +static gboolean +la_handler_service_handle_consumer_lifecycle_request (ShutdownConsumer *consumer, + GDBusMethodInvocation *invocation, + guint request, + guint request_id, + ShutdownClient *client) +{ + LAHandlerServiceData *data; + LAHandlerService *service; + gchar *unit_name; + + g_return_val_if_fail (IS_SHUTDOWN_CONSUMER (consumer), FALSE); + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE); + g_return_val_if_fail (IS_SHUTDOWN_CLIENT (client), FALSE); + + /* get the service from the shutdown client */ + service = g_object_get_data (G_OBJECT (client), "la-handler-service"); + + /* look up the unit name associated with this shutdown client */ + unit_name = g_hash_table_lookup (service->clients_to_units, client); + + if (unit_name != NULL) + { + data = la_handler_service_data_new (service, NULL, request_id); + + /* stop this unit now */ + job_manager_stop (service->job_manager, unit_name, NULL, + la_handler_service_handle_consumer_lifecycle_request_finish, + data); + + /* let the NSM know that we are working on this request */ + shutdown_consumer_complete_lifecycle_request (consumer, invocation, + NSM_ERROR_STATUS_RESPONSE_PENDING); + } + else + { + /* NSM asked us to shutdown a shutdown consumer we did not register; + * make it aware by returning an error */ + shutdown_consumer_complete_lifecycle_request (consumer, invocation, + NSM_ERROR_STATUS_ERROR); + } + + return TRUE; +} + + + +static void +la_handler_service_handle_consumer_lifecycle_request_finish (JobManager *manager, + const gchar *unit, + const gchar *result, + GError *error, + gpointer user_data) +{ + LAHandlerServiceData *data = (LAHandlerServiceData *)user_data; + GError *err = NULL; + gchar *log_text; + gint error_status = NSM_ERROR_STATUS_OK; + gint status = NSM_ERROR_STATUS_OK; + + g_return_if_fail (IS_JOB_MANAGER (manager)); + g_return_if_fail (unit != NULL && *unit != '\0'); + g_return_if_fail (result != NULL && *result != '\0'); + g_return_if_fail (data != NULL); + + /* log that we are completing a lifecycle request */ + log_text = g_strdup_printf ("Completing lifecycle request: request id %u", + data->request_id); + DLT_LOG (la_handler_context, DLT_LOG_INFO, DLT_STRING (log_text)); + g_free (log_text); + + /* log an error if shutting down the consumer has failed */ + if (error != NULL) + { + log_text = g_strdup_printf ("Failed to shutdown a shutdown consumer: %s", + error->message); + DLT_LOG (la_handler_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + + /* send an error back to the NSM */ + status = NSM_ERROR_STATUS_ERROR; + } + + /* log an error if systemd failed to stop the consumer */ + if (g_strcmp0 (result, "failed") == 0) + { + DLT_LOG (la_handler_context, DLT_LOG_ERROR, + DLT_STRING ("Failed to shutdown a shutdown consumer")); + + /* send an error back to the NSM */ + status = NSM_ERROR_STATUS_ERROR; + } + + /* let the NSM know that we have handled the lifecycle request */ + if (!nsm_consumer_call_lifecycle_request_complete_sync (data->service->nsm_consumer, + data->request_id, status, + &error_status, NULL, &err)) + { + log_text = g_strdup_printf ("Failed to notify Node State Manager about completed " + "lifecycle request: request id %u: %s", + data->request_id, err->message); + DLT_LOG (la_handler_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (err); + } + else if (error_status == NSM_ERROR_STATUS_OK) + { + log_text = g_strdup_printf ("Successfully notified Node State Manager about " + "completed lifecycle request: request id %u", + data->request_id); + DLT_LOG (la_handler_context, DLT_LOG_INFO, DLT_STRING (log_text)); + g_free (log_text); + } + else + { + log_text = g_strdup_printf ("Failed to notify Node State Manager about completed " + "lifecycle request: request id %u, error status %u", + data->request_id, error_status); + DLT_LOG (la_handler_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + } + + la_handler_service_data_unref (data); +} + + + +static LAHandlerServiceData * +la_handler_service_data_new (LAHandlerService *service, + GDBusMethodInvocation *invocation, + guint request_id) +{ + LAHandlerServiceData *data; + + data = g_slice_new0 (LAHandlerServiceData); + if (service != NULL) + data->service = g_object_ref (service); + if (invocation != NULL) + data->invocation = g_object_ref (invocation); + data->request_id = request_id; + + return data; +} + + + +static void +la_handler_service_data_unref (LAHandlerServiceData *data) +{ + if (data == NULL) + return; + + if (data->invocation != NULL) + g_object_unref (data->invocation); + if (data->service != NULL) + g_object_unref (data->service); + g_slice_free (LAHandlerServiceData, data); +} + + + +LAHandlerService * +la_handler_service_new (GDBusConnection *connection, + JobManager *job_manager) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (IS_JOB_MANAGER (job_manager), NULL); + + return g_object_new (LA_HANDLER_TYPE_SERVICE, + "connection", connection, + "job-manager", job_manager, + NULL); +} + + +gboolean +la_handler_service_start (LAHandlerService *service, + GError **error) +{ + g_return_val_if_fail (LA_HANDLER_IS_SERVICE (service), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* announce the org.genivi.BootManager1.LegacyAppHandler service on the bus */ + return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (service->interface), + service->connection, + "/org/genivi/BootManager1/LegacyAppHandler", + error); +} + + + +NSMConsumer * +la_handler_service_get_nsm_consumer (LAHandlerService *service) +{ + g_return_val_if_fail (LA_HANDLER_IS_SERVICE (service), NULL); + + return service->nsm_consumer; +} + + + +void +la_handler_service_deregister_consumers (LAHandlerService *service) +{ + GHashTableIter iter; + ShutdownClient *client; + const gchar *bus_name; + const gchar *object_path; + const gchar *unit; + GError *error = NULL; + gchar *log_text; + gint error_code; + gint shutdown_mode; + + g_return_if_fail (LA_HANDLER_IS_SERVICE (service)); + + g_hash_table_iter_init (&iter, service->clients_to_units); + while (g_hash_table_iter_next (&iter, (gpointer *)&client, (gpointer *)&unit)) + { + /* extract data from the current client */ + bus_name = shutdown_client_get_bus_name (client); + object_path = shutdown_client_get_object_path (client); + shutdown_mode = shutdown_client_get_shutdown_mode (client); + + /* unregister the shutdown client */ + nsm_consumer_call_un_register_shutdown_client_sync (service->nsm_consumer, + bus_name, object_path, + shutdown_mode, &error_code, + NULL, &error); + + if (error != NULL) + { + log_text = g_strdup_printf ("Failed to unregister shutdown client %s " + "for unit %s: %s", object_path, unit, + error->message); + DLT_LOG (la_handler_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + else if (error_code != NSM_ERROR_STATUS_OK) + { + log_text = g_strdup_printf ("Failed to unregister shutdown client %s " + "for unit %s: error code %d", object_path, unit, + error_code); + DLT_LOG (la_handler_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + } + } +} diff --git a/node-startup-controller/la-handler-service.h b/node-startup-controller/la-handler-service.h new file mode 100644 index 0000000..831fcca --- /dev/null +++ b/node-startup-controller/la-handler-service.h @@ -0,0 +1,49 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef __LA_HANDLER_SERVICE_H__ +#define __LA_HANDLER_SERVICE_H__ + +#include <gio/gio.h> + +#include <common/nsm-consumer-dbus.h> + +#include <node-startup-controller/job-manager.h> + +G_BEGIN_DECLS + +#define LA_HANDLER_TYPE_SERVICE (la_handler_service_get_type ()) +#define LA_HANDLER_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), LA_HANDLER_TYPE_SERVICE, LAHandlerService)) +#define LA_HANDLER_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), LA_HANDLER_TYPE_SERVICE, LAHandlerServiceClass)) +#define LA_HANDLER_IS_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LA_HANDLER_TYPE_SERVICE)) +#define LA_HANDLER_IS_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LA_HANDLER_TYPE_SERVICE)) +#define LA_HANDLER_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), LA_HANDLER_TYPE_SERVICE, LAHandlerServiceClass)) + +typedef struct _LAHandlerServiceClass LAHandlerServiceClass; +typedef struct _LAHandlerService LAHandlerService; + +GType la_handler_service_get_type (void) G_GNUC_CONST; + +LAHandlerService *la_handler_service_new (GDBusConnection *connection, + JobManager *job_manager) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; +gboolean la_handler_service_start (LAHandlerService *service, + GError **error); +void la_handler_service_register (LAHandlerService *service, + const gchar *unit, + const gchar *mode, + guint timeout, + GAsyncReadyCallback callback, + gpointer user_data); +NSMConsumer *la_handler_service_get_nsm_consumer (LAHandlerService *service); +void la_handler_service_deregister_consumers (LAHandlerService *service); + +G_END_DECLS + +#endif /* !__LA_HANDLER_SERVICE_H__ */ + diff --git a/node-startup-controller/luc-starter.c b/node-startup-controller/luc-starter.c new file mode 100644 index 0000000..071f174 --- /dev/null +++ b/node-startup-controller/luc-starter.c @@ -0,0 +1,663 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <glib-object.h> +#include <gio/gio.h> + +#include <dlt/dlt.h> + +#include <common/nsm-lifecycle-control-dbus.h> + +#include <node-startup-controller/boot-manager-service.h> +#include <node-startup-controller/job-manager.h> +#include <node-startup-controller/luc-starter.h> + + + +DLT_IMPORT_CONTEXT (boot_manager_context); + + + +/* signal identifiers */ +enum +{ + SIGNAL_LUC_GROUPS_STARTED, + LAST_SIGNAL, +}; + + + +/* property identifiers */ +enum +{ + PROP_0, + PROP_JOB_MANAGER, + PROP_BOOT_MANAGER_SERVICE, +}; + + +static void luc_starter_constructed (GObject *object); +static void luc_starter_finalize (GObject *object); +static void luc_starter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void luc_starter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static gint luc_starter_compare_luc_types (gconstpointer a, + gconstpointer b, + gpointer user_data); +static void luc_starter_start_next_group (LUCStarter *starter); +static void luc_starter_start_app (const gchar *app, + LUCStarter *starter); +static void luc_starter_start_app_finish (JobManager *manager, + const gchar *unit, + const gchar *result, + GError *error, + gpointer user_data); +static void luc_starter_cancel_start (const gchar *app, + GCancellable *cancellable, + gpointer user_data); +static void luc_starter_check_luc_required_finish (GObject *object, + GAsyncResult *res, + gpointer user_data); +static void luc_starter_start_groups_for_real (LUCStarter *starter); + + + +struct _LUCStarterClass +{ + GObjectClass __parent__; +}; + +struct _LUCStarter +{ + GObject __parent__; + + JobManager *job_manager; + BootManagerService *boot_manager_service; + NSMLifecycleControl *nsm_lifecycle_control; + + GArray *prioritised_types; + + GArray *start_order; + GHashTable *start_groups; + + GHashTable *cancellables; +}; + + + +static guint luc_starter_signals[LAST_SIGNAL]; + + + +G_DEFINE_TYPE (LUCStarter, luc_starter, G_TYPE_OBJECT); + + + +static void +luc_starter_class_init (LUCStarterClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->constructed = luc_starter_constructed; + gobject_class->finalize = luc_starter_finalize; + gobject_class->get_property = luc_starter_get_property; + gobject_class->set_property = luc_starter_set_property; + + g_object_class_install_property (gobject_class, + PROP_JOB_MANAGER, + g_param_spec_object ("job-manager", + "Job Manager", + "The internal handler of Start()" + " and Stop() jobs", + TYPE_JOB_MANAGER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_BOOT_MANAGER_SERVICE, + g_param_spec_object ("boot-manager-service", + "boot-manager-service", + "boot-manager-service", + BOOT_MANAGER_TYPE_SERVICE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + luc_starter_signals[SIGNAL_LUC_GROUPS_STARTED] = + g_signal_new ("luc-groups-started", + TYPE_LUC_STARTER, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + + + +static void +luc_starter_init (LUCStarter *starter) +{ + /* allocate data structures for the start order and groups */ + starter->start_order = g_array_new (FALSE, TRUE, sizeof (gint)); + starter->start_groups = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) g_ptr_array_free); + + /* allocate a mapping of app names to correspoding cancellables */ + starter->cancellables = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_object_unref); +} + + + +static void +luc_starter_constructed (GObject *object) +{ + LUCStarter *starter = LUC_STARTER (object); + GError *error = NULL; + gchar **types; + gchar *log_text; + guint n; + gint type; + + /* connect to the node state manager */ + starter->nsm_lifecycle_control = + nsm_lifecycle_control_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "com.contiautomotive.NodeStateManager", + "/com/contiautomotive/NodeStateManager/LifecycleControl", + NULL, &error); + if (error != NULL) + { + log_text = g_strdup_printf ("Failed to connect to the NSM lifecycle control: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + + /* parse the prioritised LUC types defined at build-time */ + types = g_strsplit (PRIORITISED_LUC_TYPES, ",", -1); + starter->prioritised_types = g_array_new (FALSE, TRUE, sizeof (gint)); + for (n = 0; types != NULL && types[n] != NULL; n++) + { + type = strtol (types[n], NULL, 10); + g_array_append_val (starter->prioritised_types, type); + } + g_strfreev (types); +} + + + +static void +luc_starter_finalize (GObject *object) +{ + LUCStarter *starter = LUC_STARTER (object); + + /* release NSMLifecycleControl */ + if (starter->nsm_lifecycle_control != NULL) + g_object_unref (starter->nsm_lifecycle_control); + + /* release start order, and groups */ + g_array_free (starter->start_order, TRUE); + g_hash_table_unref (starter->start_groups); + + /* release the cancellables */ + g_hash_table_unref (starter->cancellables); + + /* free the prioritised types array */ + g_array_free (starter->prioritised_types, TRUE); + + /* release the job manager */ + g_object_unref (starter->job_manager); + + /* release the boot manager service */ + g_object_unref (starter->boot_manager_service); + + (*G_OBJECT_CLASS (luc_starter_parent_class)->finalize) (object); +} + + + +static void +luc_starter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + LUCStarter *starter = LUC_STARTER (object); + + switch (prop_id) + { + case PROP_JOB_MANAGER: + g_value_set_object (value, starter->job_manager); + break; + case PROP_BOOT_MANAGER_SERVICE: + g_value_set_object (value, starter->boot_manager_service); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +luc_starter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + LUCStarter *starter = LUC_STARTER (object); + + switch (prop_id) + { + case PROP_JOB_MANAGER: + starter->job_manager = g_value_dup_object (value); + break; + case PROP_BOOT_MANAGER_SERVICE: + starter->boot_manager_service = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static gint +luc_starter_compare_luc_types (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + LUCStarter *starter = LUC_STARTER (user_data); + guint n; + gint type; + gint type_a = *(gint *)a; + gint type_b = *(gint *)b; + gint pos_a = G_MAXINT; + gint pos_b = G_MAXINT; + + /* try to find the first type in the prioritised types list */ + for (n = 0; pos_a == G_MAXINT && n < starter->prioritised_types->len; n++) + { + type = g_array_index (starter->prioritised_types, gint, n); + if (type == type_a) + pos_a = n; + } + + /* try to find the second type in the prioritised types list */ + for (n = 0; pos_b == G_MAXINT && n < starter->prioritised_types->len; n++) + { + type = g_array_index (starter->prioritised_types, gint, n); + if (type == type_b) + pos_b = n; + } + + /* this statement has the following effect when sorting: + * - a and b are prioritised -> prioritization order is preserved + * - a is prioritised, b isn't -> negative return value, a comes first + * - b is prioritised, a isn't -> positive return value, b comes first + * - neither a nor b prioritised -> return value is 0, arbitrary order + */ + return pos_a - pos_b; +} + + + +static void +luc_starter_start_next_group (LUCStarter *starter) +{ + GPtrArray *apps; + gint group; + + g_return_if_fail (IS_LUC_STARTER (starter)); + g_return_if_fail (starter->start_order->len > 0); + + /* fetch the next group */ + group = g_array_index (starter->start_order, gint, 0); + + g_debug ("start group %i", group); + + /* look up the apps for the group */ + apps = g_hash_table_lookup (starter->start_groups, GINT_TO_POINTER (group)); + if (apps != NULL) + { + /* launch all the applications in the group asynchronously */ + g_ptr_array_foreach (apps, (GFunc) luc_starter_start_app, starter); + } +} + + + +static void +luc_starter_start_app (const gchar *app, + LUCStarter *starter) +{ + GCancellable *cancellable; + + g_return_if_fail (app != NULL && *app != '\0'); + g_return_if_fail (IS_LUC_STARTER (starter)); + + g_debug ("start app '%s'", app); + + /* create the new cancellable */ + cancellable = g_cancellable_new (); + + /* store an association between each app and its cancellable, so that it is possible to + * call g_cancellable_cancel() for each respective app. */ + g_hash_table_insert (starter->cancellables, g_strdup (app), cancellable); + + /* start the service, passing the cancellable */ + job_manager_start (starter->job_manager, app, cancellable, + luc_starter_start_app_finish, + g_object_ref (starter)); +} + + + +static void +luc_starter_start_app_finish (JobManager *manager, + const gchar *unit, + const gchar *result, + GError *error, + gpointer user_data) +{ + LUCStarter *starter = LUC_STARTER (user_data); + GPtrArray *apps; + gboolean app_found = FALSE; + gchar *message; + guint n; + gint group; + + g_return_if_fail (IS_JOB_MANAGER (manager)); + g_return_if_fail (unit != NULL && *unit != '\0'); + g_return_if_fail (IS_LUC_STARTER (user_data)); + g_return_if_fail (starter->start_order->len > 0); + + g_debug ("start app '%s' finish", unit); + + /* respond to errors */ + if (error != NULL) + { + message = g_strdup_printf ("Failed to start the LUC application \"%s\": %s", + unit, error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (message)); + g_free (message); + } + + /* get the current start group */ + group = g_array_index (starter->start_order, gint, 0); + + /* look up the apps for this group */ + apps = g_hash_table_lookup (starter->start_groups, GINT_TO_POINTER (group)); + if (apps != NULL) + { + /* try to find the current app in the group */ + for (n = 0; !app_found && n < apps->len; n++) + { + if (g_strcmp0 (unit, g_ptr_array_index (apps, n)) == 0) + app_found = TRUE; + } + + /* remove the app from the group */ + if (app_found) + g_ptr_array_remove_index (apps, n-1); + + /* check if this was the last app in the group to be started */ + if (apps->len == 0) + { + g_debug ("start group %i finish", group); + + /* remove the group from the groups and the order */ + g_hash_table_remove (starter->start_groups, GINT_TO_POINTER (group)); + g_array_remove_index (starter->start_order, 0); + + /* check if we have more groups to start */ + if (starter->start_order->len > 0) + { + /* we do, so start the next group now */ + luc_starter_start_next_group (starter); + } + else + { + /* no, we are finished; notify others */ + g_signal_emit (starter, luc_starter_signals[SIGNAL_LUC_GROUPS_STARTED], + 0, NULL); + } + } + } + + /* remove the association between an app and its cancellable, because the app has + * finished starting, so cannot be cancelled any more */ + g_hash_table_remove (starter->cancellables, unit); + + /* release the LUCStarter because the operation is finished */ + g_object_unref (starter); +} + + + +static void +luc_starter_cancel_start (const gchar *app, + GCancellable *cancellable, + gpointer user_data) +{ + g_cancellable_cancel (cancellable); +} + + + +static void +luc_starter_check_luc_required_finish (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + NSMLifecycleControl *nsm_lifecycle_control = NSM_LIFECYCLE_CONTROL (object); + LUCStarter *starter = LUC_STARTER (user_data); + gboolean luc_required = TRUE; + GError *error = NULL; + gchar *log_text; + + g_return_if_fail (IS_NSM_LIFECYCLE_CONTROL (nsm_lifecycle_control)); + g_return_if_fail (G_IS_ASYNC_RESULT (res)); + g_return_if_fail (IS_LUC_STARTER (starter)); + + /* finish the checking for reloading the LUC */ + if (!nsm_lifecycle_control_call_check_luc_required_finish (nsm_lifecycle_control, + &luc_required, res, &error)) + { + log_text = g_strdup_printf ("Failed checking whether the LUC is required: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_clear_error (&error); + + DLT_LOG (boot_manager_context, DLT_LOG_INFO, + DLT_STRING ("Assuming that we should start the LUC")); + + /* start all the LUC groups now */ + luc_starter_start_groups_for_real (starter); + } + else + { + /* check whether we need to start the LUC or not */ + if (luc_required) + { + DLT_LOG (boot_manager_context, DLT_LOG_INFO, + DLT_STRING ("LUC is required, starting it now")); + + /* start all the LUC groups now */ + luc_starter_start_groups_for_real (starter); + } + else + { + /* LUC is not required, log this information */ + DLT_LOG (boot_manager_context, DLT_LOG_INFO, + DLT_STRING ("LUC is not required")); + + /* notify others that we have started the LUC groups; we haven't + * in this case but the call of luc_starter_start_groups() may + * still want to be notified that the call has been processed */ + g_signal_emit (starter, luc_starter_signals[SIGNAL_LUC_GROUPS_STARTED], + 0, NULL); + } + } +} + + + +static void +luc_starter_start_groups_for_real (LUCStarter *starter) +{ + GVariantIter iter; + GPtrArray *group_apps; + GVariant *context; + GError *error = NULL; + GList *groups; + GList *lp; + gchar **apps; + gchar *log_text; + guint n; + gint group; + gint type; + + g_return_if_fail (IS_LUC_STARTER (starter)); + + /* to load the LUC is required */ + g_debug ("prioritised types:"); + for (n = 0; n < starter->prioritised_types->len; n++) + g_debug (" %i", g_array_index (starter->prioritised_types, gint, n)); + + /* clear the start order */ + if (starter->start_order->len > 0) + g_array_remove_range (starter->start_order, 0, starter->start_order->len); + + /* clear the start groups */ + g_hash_table_remove_all (starter->start_groups); + + /* clear the mapping between apps and their cancellables */ + g_hash_table_remove_all (starter->cancellables); + + /* get the current last user context */ + context = boot_manager_service_read_luc (starter->boot_manager_service, &error); + if (error != NULL) + { + if (error->code == G_IO_ERROR_NOT_FOUND) + { + DLT_LOG (boot_manager_context, DLT_LOG_INFO, + DLT_STRING ("Boot manager could not find the last user context")); + } + else + { + log_text = g_strdup_printf ("Error reading last user context: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + } + g_error_free (error); + return; + } + + /* create groups for all types in the LUC */ + g_variant_iter_init (&iter, context); + while (g_variant_iter_loop (&iter, "{i^as}", &type, &apps)) + { + group_apps = g_ptr_array_new_with_free_func (g_free); + + for (n = 0; apps != NULL && apps[n] != NULL; n++) + g_ptr_array_add (group_apps, g_strdup (apps[n])); + + g_hash_table_insert (starter->start_groups, GINT_TO_POINTER (type), group_apps); + } + + /* release the last user context */ + g_variant_unref (context); + + /* generate the start order by sorting the LUC types according to + * the prioritised types */ + groups = g_hash_table_get_keys (starter->start_groups); + for (lp = groups; lp != NULL; lp = lp->next) + { + group = GPOINTER_TO_INT (lp->data); + g_array_append_val (starter->start_order, group); + } + g_array_sort_with_data (starter->start_order, luc_starter_compare_luc_types, starter); + + g_debug ("start groups (ordered):"); + for (n = 0; n < starter->start_order->len; n++) + g_debug (" %i", g_array_index (starter->start_order, gint, n)); + + if (starter->start_order->len > 0) + luc_starter_start_next_group (starter); +} + + + +LUCStarter * +luc_starter_new (JobManager *job_manager, + BootManagerService *boot_manager_service) +{ + g_return_val_if_fail (IS_JOB_MANAGER (job_manager), NULL); + g_return_val_if_fail (BOOT_MANAGER_IS_SERVICE (boot_manager_service), NULL); + + return g_object_new (TYPE_LUC_STARTER, + "job-manager", job_manager, + "boot-manager-service", boot_manager_service, + NULL); +} + + + +void +luc_starter_start_groups (LUCStarter *starter) +{ + g_return_if_fail (IS_LUC_STARTER (starter)); + + /* check whether the NSMLifecycleProxy is available or not */ + if (starter->nsm_lifecycle_control != NULL) + { + /* check with NSM whether to load the LUC */ + nsm_lifecycle_control_call_check_luc_required (starter->nsm_lifecycle_control, NULL, + luc_starter_check_luc_required_finish, + starter); + } + else + { + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, + DLT_STRING ("NSM unavailable, starting the LUC unconditionally")); + + /* start all the LUC groups now */ + luc_starter_start_groups_for_real (starter); + } +} + + + +void +luc_starter_cancel (LUCStarter *starter) +{ + g_hash_table_foreach (starter->cancellables, (GHFunc) luc_starter_cancel_start, NULL); +} diff --git a/node-startup-controller/luc-starter.h b/node-startup-controller/luc-starter.h new file mode 100644 index 0000000..bcb145e --- /dev/null +++ b/node-startup-controller/luc-starter.h @@ -0,0 +1,38 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef __LUC_STARTER_H__ +#define __LUC_STARTER_H__ + +#include <node-startup-controller/boot-manager-service.h> +#include <node-startup-controller/job-manager.h> + +G_BEGIN_DECLS + +#define TYPE_LUC_STARTER (luc_starter_get_type ()) +#define LUC_STARTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_LUC_STARTER, LUCStarter)) +#define LUC_STARTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_LUC_STARTER, LUCStarterClass)) +#define IS_LUC_STARTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_LUC_STARTER)) +#define IS_LUC_STARTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_LUC_STARTER)) +#define LUC_STARTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_LUC_STARTER, LUCStarterClass)) + +typedef struct _LUCStarterClass LUCStarterClass; +typedef struct _LUCStarter LUCStarter; + +GType luc_starter_get_type (void) G_GNUC_CONST; + +LUCStarter *luc_starter_new (JobManager *job_manager, + BootManagerService *boot_manager_service) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; +void luc_starter_start_groups (LUCStarter *starter); +void luc_starter_cancel (LUCStarter *starter); + +G_END_DECLS + +#endif /* !__LUC_STARTER_H__ */ + diff --git a/node-startup-controller/main.c b/node-startup-controller/main.c new file mode 100644 index 0000000..348597b --- /dev/null +++ b/node-startup-controller/main.c @@ -0,0 +1,195 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <glib.h> +#include <gio/gio.h> + +#include <dlt/dlt.h> + +#include <node-startup-controller/boot-manager-application.h> +#include <node-startup-controller/boot-manager-dbus.h> +#include <node-startup-controller/boot-manager-service.h> +#include <node-startup-controller/la-handler-service.h> +#include <node-startup-controller/systemd-manager-dbus.h> +#include <node-startup-controller/target-startup-monitor.h> + + + +DLT_DECLARE_CONTEXT (boot_manager_context); +DLT_DECLARE_CONTEXT (la_handler_context); + + + +static void +unregister_dlt (void) +{ + DLT_UNREGISTER_CONTEXT (boot_manager_context); + DLT_UNREGISTER_CONTEXT (la_handler_context); + DLT_UNREGISTER_APP (); +} + + + +int +main (int argc, + char **argv) +{ + BootManagerApplication *application; + TargetStartupMonitor *target_startup_monitor; + BootManagerService *boot_manager_service; + LAHandlerService *la_handler_service; + GDBusConnection *connection; + SystemdManager *systemd_manager; + JobManager *job_manager; + GMainLoop *main_loop; + GError *error = NULL; + gchar *msg; + + /* register the application and context in DLT */ + DLT_REGISTER_APP ("BMGR", "GENIVI Boot Manager"); + DLT_REGISTER_CONTEXT (boot_manager_context, "MGR", + "Context of the boot manager itself"); + DLT_REGISTER_CONTEXT (la_handler_context, "LAH", + "Context of the legacy application handler that hooks legacy " + "applications up with the shutdown concept of the Node State " + "Manager"); + + /* have DLT unregistered at exit */ + atexit (unregister_dlt); + + /* initialize the GType type system */ + g_type_init (); + + /* attempt to connect to D-Bus */ + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (connection == NULL) + { + msg = g_strdup_printf ("Failed to connect to the system bus: %s", error->message); + DLT_LOG (boot_manager_context, DLT_LOG_FATAL, DLT_STRING (msg)); + g_free (msg); + + /* clean up */ + g_error_free (error); + + return EXIT_FAILURE; + } + + /* attempt to connect to the systemd manager */ + systemd_manager = + systemd_manager_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + NULL, &error); + if (systemd_manager == NULL) + { + msg = g_strdup_printf ("Failed to connect to the systemd manager: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_FATAL, DLT_STRING (msg)); + g_free (msg); + + /* clean up */ + g_error_free (error); + g_object_unref (connection); + + return EXIT_FAILURE; + } + + /* subscribe to the systemd manager */ + if (!systemd_manager_call_subscribe_sync (systemd_manager, NULL, &error)) + { + msg = g_strdup_printf ("Failed to subscribe to the systemd manager: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_FATAL, DLT_STRING (msg)); + g_free (msg); + + /* clean up */ + g_error_free (error); + g_object_unref (connection); + + return EXIT_FAILURE; + } + + /* instantiate the boot manager service implementation */ + boot_manager_service = boot_manager_service_new (connection); + + /* attempt to start the boot manager service */ + if (!boot_manager_service_start_up (boot_manager_service, &error)) + { + msg = g_strdup_printf ("Failed to start the boot manager service: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (msg)); + g_free (msg); + + /* clean up */ + g_error_free (error); + g_object_unref (boot_manager_service); + g_object_unref (systemd_manager); + g_object_unref (connection); + + return EXIT_FAILURE; + } + + /* instantiate the job manager */ + job_manager = job_manager_new (connection, systemd_manager); + + /* instantiate the legacy app handler */ + la_handler_service = la_handler_service_new (connection, job_manager); + + /* start the legacy app handler */ + if (!la_handler_service_start (la_handler_service, &error)) + { + msg = g_strdup_printf ("Failed to start the legacy app handler service: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (msg)); + g_free (msg); + + /* clean up */ + g_clear_error (&error); + g_object_unref (la_handler_service); + g_object_unref (job_manager); + g_object_unref (boot_manager_service); + g_object_unref (systemd_manager); + g_object_unref (connection); + + return EXIT_FAILURE; + } + + /* create the main loop */ + main_loop = g_main_loop_new (NULL, FALSE); + + /* create the target startup monitor */ + target_startup_monitor = target_startup_monitor_new (systemd_manager); + + /* create and run the main application */ + application = boot_manager_application_new (main_loop, connection, job_manager, + la_handler_service, boot_manager_service); + + /* run the main loop */ + g_main_loop_run (main_loop); + g_main_loop_unref (main_loop); + + /* release allocated objects */ + g_object_unref (application); + g_object_unref (target_startup_monitor); + g_object_unref (systemd_manager); + g_object_unref (job_manager); + g_object_unref (boot_manager_service); + g_object_unref (connection); + + return EXIT_SUCCESS; +} diff --git a/node-startup-controller/org.genivi.BootManager1.service.in b/node-startup-controller/org.genivi.BootManager1.service.in new file mode 100644 index 0000000..04d1ee7 --- /dev/null +++ b/node-startup-controller/org.genivi.BootManager1.service.in @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.genivi.BootManager1 +SystemdService = boot-manager.service +Exec=@libdir@/boot-manager-@BOOT_MANAGER_VERSION_API@/boot-manager diff --git a/node-startup-controller/systemd-manager-dbus.xml b/node-startup-controller/systemd-manager-dbus.xml new file mode 100644 index 0000000..29337e1 --- /dev/null +++ b/node-startup-controller/systemd-manager-dbus.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/org/freedesktop/systemd1"> + <interface name="org.freedesktop.systemd1.Manager"> + <method name="GetUnit"> + <arg name="name" type="s" direction="in"/> + <arg name="unit" type="o" direction="out"/> + </method> + + <method name="StartUnit"> + <arg name="name" type="s" direction="in"/> + <arg name="mode" type="s" direction="in"/> + <arg name="job" type="o" direction="out"/> + </method> + + <method name="StopUnit"> + <arg name="name" type="s" direction="in"/> + <arg name="mode" type="s" direction="in"/> + <arg name="job" type="o" direction="out"/> + </method> + + <method name="ListJobs"> + <arg name="jobs" type="a(usssoo)" direction="out"/> + </method> + + <method name="Subscribe"/> + + <method name="Unsubscribe"/> + + <signal name="JobRemoved"> + <arg name="id" type="u"/> + <arg name="job" type="o"/> + <arg name="unit" type="s"/> + <arg name="result" type="s"/> + </signal> + </interface> +</node> diff --git a/node-startup-controller/systemd-unit-dbus.xml b/node-startup-controller/systemd-unit-dbus.xml new file mode 100644 index 0000000..1461a3b --- /dev/null +++ b/node-startup-controller/systemd-unit-dbus.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/org/freedesktop/systemd1"> + <interface name="org.freedesktop.systemd1.Unit"> + <property name="ActiveState" type="s" access="read"/> + <property name="Id" type="s" access="read"/> + </interface> +</node> diff --git a/node-startup-controller/target-startup-monitor.c b/node-startup-controller/target-startup-monitor.c new file mode 100644 index 0000000..722ab56 --- /dev/null +++ b/node-startup-controller/target-startup-monitor.c @@ -0,0 +1,457 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib-object.h> +#include <gio/gio.h> + +#include <dlt/dlt.h> + +#include <common/nsm-enum-types.h> +#include <common/nsm-lifecycle-control-dbus.h> + +#include <node-startup-controller/boot-manager-dbus.h> +#include <node-startup-controller/target-startup-monitor.h> +#include <node-startup-controller/systemd-unit-dbus.h> + + + +DLT_IMPORT_CONTEXT (boot_manager_context); + + + +/* property identifier */ +enum +{ + PROP_0, + PROP_SYSTEMD_MANAGER, +}; + + + +typedef struct _GetUnitData GetUnitData; + + + +static void target_startup_monitor_finalize (GObject *object); +static void target_startup_monitor_constructed (GObject *object); +static void target_startup_monitor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void target_startup_monitor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void target_startup_monitor_job_removed (SystemdManager *manager, + guint id, + const gchar *job_name, + const gchar *unit, + const gchar *result, + TargetStartupMonitor *monitor); +static void target_startup_monitor_get_unit_finish (GObject *object, + GAsyncResult *res, + gpointer user_data); +static void target_startup_monitor_unit_proxy_new_finish (GObject *object, + GAsyncResult *res, + gpointer user_data); +static void target_startup_monitor_set_node_state (TargetStartupMonitor *monitor, + NSMNodeState state); +static void target_startup_monitor_set_node_state_finish (GObject *object, + GAsyncResult *res, + gpointer user_data); + + + +struct _TargetStartupMonitorClass +{ + GObjectClass __parent__; +}; + +struct _TargetStartupMonitor +{ + GObject __parent__; + + SystemdManager *systemd_manager; + + NSMLifecycleControl *nsm_lifecycle_control; + + /* list of systemd units for the targets we are interested in */ + GList *units; + + /* map of systemd target names to corresponding node states */ + GHashTable *targets_to_states; +}; + +struct _GetUnitData +{ + TargetStartupMonitor *monitor; + gchar *unit_name; + gchar *object_path; +}; + + + +G_DEFINE_TYPE (TargetStartupMonitor, target_startup_monitor, G_TYPE_OBJECT); + + + +static void +target_startup_monitor_class_init (TargetStartupMonitorClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = target_startup_monitor_finalize; + gobject_class->constructed = target_startup_monitor_constructed; + gobject_class->get_property = target_startup_monitor_get_property; + gobject_class->set_property = target_startup_monitor_set_property; + + g_object_class_install_property (gobject_class, + PROP_SYSTEMD_MANAGER, + g_param_spec_object ("systemd-manager", + "systemd-manager", + "systemd-manager", + TYPE_SYSTEMD_MANAGER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + + + +static void +target_startup_monitor_init (TargetStartupMonitor *monitor) +{ + GError *error = NULL; + gchar *log_text; + + /* create proxy to talk to the Node State Manager's lifecycle control */ + monitor->nsm_lifecycle_control = + nsm_lifecycle_control_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "com.contiautomotive.NodeStateManager", + "/com/contiautomotive/NodeStateManager/LifecycleControl", + NULL, &error); + if (error != NULL) + { + log_text = g_strdup_printf ("Failed to connect to the NSM lifecycle control: %s", + error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + + /* set the initial state to base running, which means that + * the mandatory.target has been started (this is done before the + * boot manager itself is brought up) */ + target_startup_monitor_set_node_state (monitor, NSM_NODE_STATE_BASE_RUNNING); + + /* create the table of targets and their node states */ + monitor->targets_to_states = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (monitor->targets_to_states, "focussed.target", + GUINT_TO_POINTER (NSM_NODE_STATE_LUC_RUNNING)); + g_hash_table_insert (monitor->targets_to_states, "unfocussed.target", + GUINT_TO_POINTER (NSM_NODE_STATE_FULLY_RUNNING)); + g_hash_table_insert (monitor->targets_to_states, "lazy.target", + GUINT_TO_POINTER (NSM_NODE_STATE_FULLY_OPERATIONAL)); +} + + + +static void +target_startup_monitor_constructed (GObject *object) +{ + TargetStartupMonitor *monitor = TARGET_STARTUP_MONITOR (object); + + g_signal_connect (monitor->systemd_manager, "job-removed", + G_CALLBACK (target_startup_monitor_job_removed), monitor); +} + + + +static void +target_startup_monitor_finalize (GObject *object) +{ + TargetStartupMonitor *monitor = TARGET_STARTUP_MONITOR (object); + GList *lp; + + /* disconnect from all the unit proxies and release them */ + for (lp = monitor->units; lp != NULL; lp = lp->next) + { + g_signal_handlers_disconnect_matched (lp->data, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, monitor); + g_object_unref (lp->data); + } + g_list_free (lp->data); + + /* release the mapping of systemd targets to node states */ + g_hash_table_destroy (monitor->targets_to_states); + + /* release the systemd manager */ + g_signal_handlers_disconnect_matched (monitor->systemd_manager, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, monitor); + g_object_unref (monitor->systemd_manager); + + if (monitor->nsm_lifecycle_control != NULL) + g_object_unref (monitor->nsm_lifecycle_control); + + (*G_OBJECT_CLASS (target_startup_monitor_parent_class)->finalize) (object); +} + + + +static void +target_startup_monitor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TargetStartupMonitor *monitor = TARGET_STARTUP_MONITOR (object); + + switch (prop_id) + { + case PROP_SYSTEMD_MANAGER: + g_value_set_object (value, monitor->systemd_manager); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +target_startup_monitor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TargetStartupMonitor *monitor = TARGET_STARTUP_MONITOR (object); + + switch (prop_id) + { + case PROP_SYSTEMD_MANAGER: + monitor->systemd_manager = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static void +target_startup_monitor_job_removed (SystemdManager *manager, + guint id, + const gchar *job_name, + const gchar *unit, + const gchar *result, + TargetStartupMonitor *monitor) +{ + GetUnitData *data; + + g_return_if_fail (IS_SYSTEMD_MANAGER (manager)); + g_return_if_fail (job_name != NULL && *job_name != '\0'); + g_return_if_fail (unit != NULL && *unit != '\0'); + g_return_if_fail (result != NULL && *result != '\0'); + g_return_if_fail (IS_TARGET_STARTUP_MONITOR (monitor)); + + /* create a temporary struct to bundle information about the unit */ + data = g_slice_new0 (GetUnitData); + data->monitor = g_object_ref (monitor); + data->unit_name = g_strdup (unit); + + /* ask systemd to return the object path for this unit */ + systemd_manager_call_get_unit (monitor->systemd_manager, unit, NULL, + target_startup_monitor_get_unit_finish, data); + + +} + + + +static void +target_startup_monitor_get_unit_finish (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + GetUnitData *data = user_data; + GError *error = NULL; + gchar *message; + gchar *object_path; + + g_return_if_fail (IS_SYSTEMD_MANAGER (object)); + g_return_if_fail (G_IS_ASYNC_RESULT (res)); + g_return_if_fail (data != NULL); + + /* finish obtaining the object path for the unit from systemd */ + if (!systemd_manager_call_get_unit_finish (SYSTEMD_MANAGER (object), &object_path, + res, &error)) + { + /* there was an error, log it */ + message = g_strdup_printf ("Failed to get unit \"%s\" from systemd: %s", + data->unit_name, error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (message)); + g_free (message); + g_error_free (error); + + /* release the get unit data */ + g_object_unref (data->monitor); + g_free (data->unit_name); + g_slice_free (GetUnitData, data); + } + else + { + message = g_strdup_printf ("Creating D-Bus proxy for unit \"%s\"", object_path); + DLT_LOG (boot_manager_context, DLT_LOG_INFO, DLT_STRING (message)); + g_free (message); + + /* remember the object path */ + data->object_path = object_path; + + /* create a proxy for this unit D-Bus object */ + systemd_unit_proxy_new (g_dbus_proxy_get_connection (G_DBUS_PROXY (data->monitor->systemd_manager)), + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.systemd1", + object_path, + NULL, + target_startup_monitor_unit_proxy_new_finish, + data); + } +} + + + +static void +target_startup_monitor_unit_proxy_new_finish (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + GetUnitData *data = user_data; + SystemdUnit *unit; + const gchar *state; + gpointer node_state; + GError *error = NULL; + gchar *message; + + g_return_if_fail (G_IS_ASYNC_RESULT (res)); + g_return_if_fail (data != NULL); + + /* finish creating the proxy for this systemd unit */ + unit = systemd_unit_proxy_new_finish (res, &error); + if (error != NULL) + { + /* there was an error, log it */ + message = g_strdup_printf ("Failed to create a D-Bus proxy for unit \"%s\": %s", + data->object_path, error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (message)); + g_error_free (error); + } + else + { + /* query the unit for its current active state */ + state = systemd_unit_get_active_state (unit); + + /* log the the active state has changed */ + message = g_strdup_printf ("Active state of unit \"%s\" changed to %s", + data->unit_name, state); + DLT_LOG (boot_manager_context, DLT_LOG_INFO, DLT_STRING (message)); + g_free (message); + + /* check if the new state is active */ + if (g_strcmp0 (state, "active") == 0) + { + /* look up the node state corresponding to this unit, if there is one */ + if (g_hash_table_lookup_extended (data->monitor->targets_to_states, + data->unit_name, NULL, &node_state)) + { + /* we do have a state for this unit, so apply it now */ + target_startup_monitor_set_node_state (data->monitor, + GPOINTER_TO_UINT (node_state)); + } + } + + /* release the unit proxy */ + g_object_unref (unit); + } + + /* free the get unit data */ + g_object_unref (data->monitor); + g_free (data->unit_name); + g_free (data->object_path); + g_slice_free (GetUnitData, data); +} + + + +static void +target_startup_monitor_set_node_state (TargetStartupMonitor *monitor, + NSMNodeState state) +{ + g_return_if_fail (IS_TARGET_STARTUP_MONITOR (monitor)); + + /* set node state in the Node State Manager */ + nsm_lifecycle_control_call_set_node_state (monitor->nsm_lifecycle_control, + (gint) state, NULL, + target_startup_monitor_set_node_state_finish, + NULL); +} + + + +static void +target_startup_monitor_set_node_state_finish (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + NSMLifecycleControl *nsm_lifecycle_control = NSM_LIFECYCLE_CONTROL (object); + GError *error = NULL; + gchar *log_text; + NSMErrorStatus error_code = NSM_ERROR_STATUS_OK; + + g_return_if_fail (IS_NSM_LIFECYCLE_CONTROL (nsm_lifecycle_control)); + g_return_if_fail (G_IS_ASYNC_RESULT (res)); + + /* finish setting the node state in the NSM */ + if (!nsm_lifecycle_control_call_set_node_state_finish (nsm_lifecycle_control, + (gint *) &error_code, res, + &error)) + { + log_text = g_strdup_printf ("Failed to set the node state: %s", error->message); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + g_error_free (error); + } + else if (error_code != NSM_ERROR_STATUS_OK) + { + log_text = g_strdup_printf ("Failed to set the node state: error code %d", + error_code); + DLT_LOG (boot_manager_context, DLT_LOG_ERROR, DLT_STRING (log_text)); + g_free (log_text); + } +} + + + +TargetStartupMonitor * +target_startup_monitor_new (SystemdManager *systemd_manager) +{ + g_return_val_if_fail (IS_SYSTEMD_MANAGER (systemd_manager), NULL); + + return g_object_new (TYPE_TARGET_STARTUP_MONITOR, + "systemd-manager", systemd_manager, + NULL); +} diff --git a/node-startup-controller/target-startup-monitor.h b/node-startup-controller/target-startup-monitor.h new file mode 100644 index 0000000..b16eb21 --- /dev/null +++ b/node-startup-controller/target-startup-monitor.h @@ -0,0 +1,32 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/* - + * Copyright (c) 2012 GENIVI. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef __TARGET_STARTUP_MONITOR_H__ +#define __TARGET_STARTUP_MONITOR_H__ + +#include <node-startup-controller/systemd-manager-dbus.h> + +G_BEGIN_DECLS + +#define TYPE_TARGET_STARTUP_MONITOR (target_startup_monitor_get_type ()) +#define TARGET_STARTUP_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_TARGET_STARTUP_MONITOR, TargetStartupMonitor)) +#define TARGET_STARTUP_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_TARGET_STARTUP_MONITOR, TargetStartupMonitorClass)) +#define IS_TARGET_STARTUP_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_TARGET_STARTUP_MONITOR)) +#define IS_TARGET_STARTUP_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_TARGET_STARTUP_MONITOR)) +#define TARGET_STARTUP_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_TARGET_STARTUP_MONITOR, TargetStartupMonitorClass)) + +typedef struct _TargetStartupMonitor TargetStartupMonitor; +typedef struct _TargetStartupMonitorClass TargetStartupMonitorClass; + +GType target_startup_monitor_get_type (void) G_GNUC_CONST; +TargetStartupMonitor *target_startup_monitor_new (SystemdManager *systemd_manager) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; + +G_END_DECLS + +#endif /* !__TARGET_STARTUP_MONITOR_H__ */ |
