diff --git a/configure.ac b/configure.ac index 172578dd78..fe54b400ff 100644 --- a/configure.ac +++ b/configure.ac @@ -756,6 +756,7 @@ src/settings/plugins/ifcfg-suse/Makefile src/settings/plugins/keyfile/Makefile src/settings/plugins/keyfile/tests/Makefile src/settings/plugins/keyfile/tests/keyfiles/Makefile +src/settings/plugins/example/Makefile src/settings/tests/Makefile src/wimax/Makefile src/backends/Makefile diff --git a/src/settings/plugins/Makefile.am b/src/settings/plugins/Makefile.am index 5df57d5ea7..d4ba12f0bf 100644 --- a/src/settings/plugins/Makefile.am +++ b/src/settings/plugins/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS=keyfile +SUBDIRS=keyfile example if TARGET_REDHAT SUBDIRS+=ifcfg-rh diff --git a/src/settings/plugins/example/Makefile.am b/src/settings/plugins/example/Makefile.am new file mode 100644 index 0000000000..452c459a4d --- /dev/null +++ b/src/settings/plugins/example/Makefile.am @@ -0,0 +1,40 @@ +INCLUDES = \ + -I$(top_srcdir)/src/settings \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_srcdir)/libnm-util \ + -I$(top_builddir)/libnm-util + +# 'noinst' here because this is an example plugin we don't want to install +noinst_LTLIBRARIES = libnm-settings-plugin-example.la + +# The actual plugins typically pull reader.c and writer.c out into +# their own static library so that unit tests can use them without +# having to build the entire plugin. But since this is a simple +# plugin we don't do that yet. + +libnm_settings_plugin_example_la_SOURCES = \ + nm-example-connection.c \ + nm-example-connection.h \ + plugin.c \ + plugin.h \ + errors.c \ + common.h \ + reader.c \ + writer.c + +libnm_settings_plugin_example_la_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(GMODULE_CFLAGS) \ + $(DBUS_CFLAGS) \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -DG_DISABLE_DEPRECATED + +libnm_settings_plugin_example_la_LIBADD = \ + $(top_builddir)/libnm-util/libnm-util.la \ + $(GLIB_LIBS) \ + $(GMODULE_LIBS) \ + $(GIO_LIBS) + +libnm_settings_plugin_ifcfg_rh_la_LDFLAGS = -module -avoid-version + diff --git a/src/settings/plugins/example/README b/src/settings/plugins/example/README new file mode 100644 index 0000000000..224814d1bd --- /dev/null +++ b/src/settings/plugins/example/README @@ -0,0 +1,35 @@ +Plugins generally have three components: + +1) plugin object: manages the individual "connections", which are + just objects wrapped around on-disk config data. The plugin handles requests + to add new connections via the NM D-Bus API, and also watches config + directories for changes to configuration data. It also handles reading and + writing the persistent hostname, if the plugin supports hostnames. Plugins + implement the NMSystemConfigInterface interface. See plugin.c. + +2) "connections": subclasses of NMSettingsConnection. They handle updates to + configuration data, deletion, etc. See NMExampleConnection.c. + +3) reader/writer code: typically a separate static library that gets linked + into the main plugin shared object, so they can be unit tested separately + from the plugin. This code should read config data from disk and create + an NMConnection from it, and be capable of taking an NMConnection and writing + out appropriate configuration data to disk. + +NM will first call the "factory" function that every module must provide, which +is nm_system_config_factory(). That function creates and returns a singleton +instance of the plugin's main object, which implements NMSystemConfigInterface. +That interface is implemented via the object definition in G_DEFINE_TYPE_EXTENDED +in plugin.c, which registers the interface setup function +system_config_interface_init(), which when called actually sets up the vtables +for the functions defined by NMSystemConfigInterface. Thus there are two +entry points into the plugin: nm_system_config_factory() and +the NMSystemConfigInterface methods. + +The plugin also emits various signals (defined by NMSystemConfigInterface) +which NetworkManager listens for. These include persistent hostname changes +(if something modified the file in which the persistent hostname is stored) +and notifications of new connections if they were created via changes to +the on-disk files. The "connection" objects can also emit signals +(defined by the NMSettingsConnection and NMConnection superclasses) when the +connections' backing storage gets changed or deleted. diff --git a/src/settings/plugins/example/common.h b/src/settings/plugins/example/common.h new file mode 100644 index 0000000000..a4197070e1 --- /dev/null +++ b/src/settings/plugins/example/common.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright 2012 Red Hat, Inc. + */ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include + +#include + +/* General info about the plugin that the code may want to use for logging + * purposes. + */ +#define EXAMPLE_PLUGIN_NAME "example" +#define EXAMPLE_PLUGIN_INFO "(c) 2012 Red Hat, Inc. To report bugs please use the NetworkManager mailing list." + +#define EXAMPLE_DIR SYSCONFDIR"/NetworkManager/example-plugin" + +/* Boilerplate stuff for the plugin's error domain. Bits of the code that + * create new errors in the plugin's domain will create errors of + * type EXAMPLE_PLUGIN_ERROR like so: + * + * error = g_error_new_literal (EXAMPLE_PLUGIN_ERROR, + * , + * "This is a really bad error."); + */ +#define EXAMPLE_PLUGIN_ERROR (example_plugin_error_quark ()) +GQuark example_plugin_error_quark (void); + +/* Prototypes for the reader/writer functions */ +NMConnection *connection_from_file (const char *filename, GError **error); + +gboolean write_connection (NMConnection *connection, + const char *existing_path, + char **out_path, + GError **error); + +#endif /* __COMMON_H__ */ + diff --git a/src/settings/plugins/example/errors.c b/src/settings/plugins/example/errors.c new file mode 100644 index 0000000000..8181f8a3a0 --- /dev/null +++ b/src/settings/plugins/example/errors.c @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright 2012 Red Hat, Inc. + */ + +#include +#include "common.h" + +/* This is boilerplate code for defining the error domain that the plugin may + * return to NetworkManager for various operations. It just registers a + * GObject "quark" (a UUID really) for the error which allows glib to keep + * track of all the different error domains. + */ +GQuark +example_plugin_error_quark (void) +{ + static GQuark error_quark = 0; + + if (G_UNLIKELY (error_quark == 0)) + error_quark = g_quark_from_static_string ("example-plugin-error-quark"); + + return error_quark; +} + + diff --git a/src/settings/plugins/example/nm-example-connection.c b/src/settings/plugins/example/nm-example-connection.c new file mode 100644 index 0000000000..e4a69f9b0a --- /dev/null +++ b/src/settings/plugins/example/nm-example-connection.c @@ -0,0 +1,191 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2012 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include + +#include "nm-system-config-interface.h" +#include "nm-dbus-glib-types.h" +#include "nm-example-connection.h" +#include "common.h" + +/* GObject boilerplate; this object is a subclass of NMSettingsConnection + * which is specified by the NM_TYPE_SETTINGS_CONNECTION bit here. That + * in turn is a subclass of NMConnection, so it ends up that NMExampleConnection + * is a subclass of NMConnection too. + */ +G_DEFINE_TYPE (NMExampleConnection, nm_example_connection, NM_TYPE_SETTINGS_CONNECTION) + +#define NM_EXAMPLE_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_EXAMPLE_CONNECTION, NMExampleConnectionPrivate)) + +/* Object private instance data */ +typedef struct { + char *path; +} NMExampleConnectionPrivate; + + +/* Creates a new object which encapsulates an on-disk connection and any + * plugin-specific operations or data. + */ +NMExampleConnection * +nm_example_connection_new (const char *full_path, + NMConnection *source, + GError **error) +{ + GObject *object; + NMExampleConnectionPrivate *priv; + NMConnection *tmp; + const char *uuid; + + g_return_val_if_fail (full_path != NULL, NULL); + + /* If we're given a connection already, prefer that instead of re-reading */ + if (source) + tmp = g_object_ref (source); + else { + /* Read the data offdisk and translate it into a simple NMConnection object */ + tmp = connection_from_file (full_path, error); + if (!tmp) + return NULL; + } + + /* Actually create the new NMExampleConnection object */ + object = (GObject *) g_object_new (NM_TYPE_EXAMPLE_CONNECTION, NULL); + if (!object) + goto out; + + priv = NM_EXAMPLE_CONNECTION_GET_PRIVATE (object); + priv->path = g_strdup (full_path); + + /* Update our settings with what was read from the file or what got passed + * in as a source NMConnection. + */ + if (!nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object), tmp, error)) { + g_object_unref (object); + object = NULL; + goto out; + } + + /* Make sure we have a UUID; just a sanity check */ + uuid = nm_connection_get_uuid (NM_CONNECTION (object)); + if (!uuid) { + g_set_error (error, EXAMPLE_PLUGIN_ERROR, 0, + "Connection in file %s had no UUID", full_path); + g_object_unref (object); + object = NULL; + } + +out: + g_object_unref (tmp); + return (NMExampleConnection *) object; +} + +const char * +nm_example_connection_get_path (NMExampleConnection *self) +{ + g_return_val_if_fail (NM_IS_EXAMPLE_CONNECTION (self), NULL); + + /* Simple accessor that returns the file path from our private instance data */ + return NM_EXAMPLE_CONNECTION_GET_PRIVATE (self)->path; +} + +static void +commit_changes (NMSettingsConnection *connection, + NMSettingsConnectionCommitFunc callback, + gpointer user_data) +{ + NMExampleConnectionPrivate *priv = NM_EXAMPLE_CONNECTION_GET_PRIVATE (connection); + char *path = NULL; + GError *error = NULL; + + /* Write the new connection data out to disk. This function passes + * back the path of the file it wrote out so that we know what that + * path is if the connection is a completely new one. + */ + if (!write_connection (NM_CONNECTION (connection), priv->path, &path, &error)) { + callback (connection, error, user_data); + g_clear_error (&error); + return; + } + + /* Update the filename if it changed */ + if (path) { + g_free (priv->path); + priv->path = path; + } + + /* Chain up to parent for generic commit stuff */ + NM_SETTINGS_CONNECTION_CLASS (nm_example_connection_parent_class)->commit_changes (connection, + callback, + user_data); +} + +static void +do_delete (NMSettingsConnection *connection, + NMSettingsConnectionDeleteFunc callback, + gpointer user_data) +{ + NMExampleConnectionPrivate *priv = NM_EXAMPLE_CONNECTION_GET_PRIVATE (connection); + + g_unlink (priv->path); + + /* Chain up to parent for generic deletion stuff */ + NM_SETTINGS_CONNECTION_CLASS (nm_example_connection_parent_class)->delete (connection, + callback, + user_data); +} + +/**************************************************************/ + +static void +nm_example_connection_init (NMExampleConnection *connection) +{ +} + +static void +finalize (GObject *object) +{ + NMExampleConnectionPrivate *priv = NM_EXAMPLE_CONNECTION_GET_PRIVATE (object); + + /* Zero out any secrets so we don't leave them in memory */ + nm_connection_clear_secrets (NM_CONNECTION (object)); + + g_free (priv->path); + + G_OBJECT_CLASS (nm_example_connection_parent_class)->finalize (object); +} + +static void +nm_example_connection_class_init (NMExampleConnectionClass *keyfile_connection_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (keyfile_connection_class); + NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (keyfile_connection_class); + + /* Tells GObject to allocate and zero our instance data pointer */ + g_type_class_add_private (keyfile_connection_class, sizeof (NMExampleConnectionPrivate)); + + /* Overrides of various superclass methods */ + object_class->finalize = finalize; + settings_class->commit_changes = commit_changes; + settings_class->delete = do_delete; +} diff --git a/src/settings/plugins/example/nm-example-connection.h b/src/settings/plugins/example/nm-example-connection.h new file mode 100644 index 0000000000..d6d09e16fe --- /dev/null +++ b/src/settings/plugins/example/nm-example-connection.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef NM_EXAMPLE_CONNECTION_H +#define NM_EXAMPLE_CONNECTION_H + +#include + +G_BEGIN_DECLS + +/* GObject boilerplate */ +#define NM_TYPE_EXAMPLE_CONNECTION (nm_example_connection_get_type ()) +#define NM_EXAMPLE_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_EXAMPLE_CONNECTION, NMExampleConnection)) +#define NM_EXAMPLE_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_EXAMPLE_CONNECTION, NMExampleConnectionClass)) +#define NM_IS_EXAMPLE_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_EXAMPLE_CONNECTION)) +#define NM_IS_EXAMPLE_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_EXAMPLE_CONNECTION)) +#define NM_EXAMPLE_CONNECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_EXAMPLE_CONNECTION, NMExampleConnectionClass)) + +typedef struct { + NMSettingsConnection parent; +} NMExampleConnection; + +typedef struct { + NMSettingsConnectionClass parent; +} NMExampleConnectionClass; + +GType nm_example_connection_get_type (void); + +/* Actual API that plugin.c will call */ +NMExampleConnection *nm_example_connection_new (const char *filename, + NMConnection *source, + GError **error); + +const char *nm_example_connection_get_path (NMExampleConnection *self); + +G_END_DECLS + +#endif /* NM_EXAMPLE_CONNECTION_H */ diff --git a/src/settings/plugins/example/plugin.c b/src/settings/plugins/example/plugin.c new file mode 100644 index 0000000000..04af03e744 --- /dev/null +++ b/src/settings/plugins/example/plugin.c @@ -0,0 +1,868 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2012 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "plugin.h" +#include "nm-system-config-interface.h" +#include "common.h" +#include "nm-example-connection.h" + +static char *plugin_get_hostname (SCPluginExample *plugin); +static void system_config_interface_init (NMSystemConfigInterface *system_config_interface_class); + +/* GObject object definition. This actually defines the object and tells + * GObject about the interfaces this object provides. Here we provide + * the "system config interface" which is the API that NetworkManager uses + * to communicate with this plugin. + * + * Interface and super/sub-class access with GObject works via casting and + * GObject magically figures out what needs to be called. So, given: + * + * SCPluginExample *plugin = ; + * + * you can call GObject methods since SCPluginExample inherits from GObject + * via the G_TYPE_OBJECT argument of G_DEFINE_TYPE_EXTENDED below: + * + * g_object_set_data (G_OBJECT (plugin), ...); + * + * and since SCPluginExample implements NMSystemConfigInterface via the + * G_IMPLEMENT_INTERFACE bit below, we can also call any methods of + * NMSystemConfigInterface (defined in NM sources in nm-system-config-interface.c): + * + * connections = nm_system_config_interface_get_connections (NM_SYSTEM_CONFIG_INTERFACE (plugin)); + * + * For the call to nm_system_config_interface_get_connections() that eventually + * ends up in the get_connections() method in this file because the + * system_config_interface_init() function sets up the vtable for this objects + * implementation of NMSystemConfigInterface. + */ +G_DEFINE_TYPE_EXTENDED (SCPluginExample, sc_plugin_example, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_SYSTEM_CONFIG_INTERFACE, system_config_interface_init)) + +/* Quick define to access the object's private data pointer; this pointer + * points to the object's instance data. + */ +#define SC_PLUGIN_EXAMPLE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SC_TYPE_PLUGIN_EXAMPLE, SCPluginExamplePrivate)) + +/* Instance data. When the object is created, a new structure of this type + * will be created and zeroed for this instance of the object to use. This + * is actually done by g_type_class_add_private() when called from the object's + * class init function. + */ +typedef struct { + /* This hash holds each connection known to this plugin */ + GHashTable *connections; + + /* A watch for changes on the directory that holds the configuration files + * so the plugin can respond to configuration changes on-the-fly and + * tell NM that the connection data has changed. Typically the plugin + * needs to monitor the directory itself (to watch for completely new files) + * while the individual connections watch their individual config files. + */ + GFileMonitor *monitor; + guint monitor_id; + + /* Tracks changes to the global NM config file, just in case our + * plugin has some specific options (like unmanaged devices) that + * might be changed at runtime. + */ + char *conf_file; + GFileMonitor *conf_file_monitor; + guint conf_file_monitor_id; + + /* Persistent hostname if the plugin supports hostnames. Normally used + * for distro plugins; ie Red Hat uses /etc/sysconfig/hostname while + * Debian uses /etc/hostname. Plugins can abstract the storage location + * and just tell NM what the persisten hostname is and when its backing + * file has changed. NM handles actually setting the hostname. + */ + char *hostname; +} SCPluginExamplePrivate; + +static NMSettingsConnection * +_internal_new_connection (SCPluginExample *self, + const char *full_path, + NMConnection *source, + GError **error) +{ + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (self); + NMExampleConnection *connection; + + g_return_val_if_fail (full_path != NULL, NULL); + + /* 'source' will usually be NULL if we're going to read the connection + * off disk. But if the new connection is coming from NetworkManager + * (ie, from a D-Bus AddConnection request) then we'll have 'source' too. + * This function expects the connection to already be written to disk + * so that the NMExampleConnection object can re-read it and intialize + * state in the same manner as when getting a change notification from + * the config directory. That simplifies things somewhat. + */ + + connection = nm_example_connection_new (full_path, source, error); + if (connection) { + g_hash_table_insert (priv->connections, + (gpointer) nm_example_connection_get_path (connection), + connection); + } + + return (NMSettingsConnection *) connection; +} + +/* Read each file in our config directory and try to create a new + * NMExamplePlugin for it. + */ +static void +read_connections (NMSystemConfigInterface *config) +{ + SCPluginExample *self = SC_PLUGIN_EXAMPLE (config); + GDir *dir; + GError *error = NULL; + const char *item; + + dir = g_dir_open (EXAMPLE_DIR, 0, &error); + if (!dir) { + PLUGIN_WARN (EXAMPLE_PLUGIN_NAME, "Cannot read directory '%s': (%d) %s", + EXAMPLE_DIR, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + return; + } + + while ((item = g_dir_read_name (dir))) { + NMSettingsConnection *connection; + char *full_path; + + /* XXX: Check file extension and ignore "~", ".tmp", ".bak", etc */ + + full_path = g_build_filename (EXAMPLE_DIR, item, NULL); + PLUGIN_PRINT (EXAMPLE_PLUGIN_NAME, "parsing %s ... ", item); + + connection = _internal_new_connection (self, full_path, NULL, &error); + if (connection) { + PLUGIN_PRINT (EXAMPLE_PLUGIN_NAME, " read connection '%s'", + nm_connection_get_id (NM_CONNECTION (connection))); + } else { + PLUGIN_PRINT (EXAMPLE_PLUGIN_NAME, " error: %s", + (error && error->message) ? error->message : "(unknown)"); + } + g_clear_error (&error); + g_free (full_path); + } + g_dir_close (dir); +} + +static void +update_connection_settings_commit_cb (NMSettingsConnection *orig, GError *error, gpointer user_data) +{ + /* If there was an error updating the connection's internal stuff, then + * we can't do anything except log it and remove the connection. This might + * happen due to invalid data, but as the data would already have been + * verified before it ever got to this plugin, we shouldn't ever get + * an error here. + */ + if (error) { + g_warning ("%s: '%s' / '%s' invalid: %d", + __func__, + error ? g_type_name (nm_connection_lookup_setting_type_by_quark (error->domain)) : "(none)", + (error && error->message) ? error->message : "(none)", + error ? error->code : -1); + g_clear_error (&error); + + nm_settings_connection_signal_remove (orig); + } +} + +static void +update_connection_settings (NMExampleConnection *orig, + NMExampleConnection *new) +{ + /* This just replaces the orig's internal settings with those from new */ + nm_settings_connection_replace_and_commit (NM_SETTINGS_CONNECTION (orig), + NM_CONNECTION (new), + update_connection_settings_commit_cb, NULL); +} + +/* Monitoring */ + +static void +remove_connection (SCPluginExample *self, + NMExampleConnection *connection, + const char *name) +{ + g_return_if_fail (connection != NULL); + g_return_if_fail (name != NULL); + + /* Removing from the hash table should drop the last reference, but since + * we need the object to stay alive across the signal emission to NM, + * we grab a temporary reference. + */ + g_object_ref (connection); + g_hash_table_remove (SC_PLUGIN_EXAMPLE_GET_PRIVATE (self)->connections, name); + + /* Tell NM the connection is gone */ + nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection)); + + /* Remove the temporary reference; connection will now be destroyed */ + g_object_unref (connection); +} + +/* Look through all connections we know about and return one with a given UUID */ +static NMExampleConnection * +find_by_uuid (SCPluginExample *self, const char *uuid) +{ + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (self); + GHashTableIter iter; + gpointer data = NULL; + + g_return_val_if_fail (uuid != NULL, NULL); + + g_hash_table_iter_init (&iter, priv->connections); + while (g_hash_table_iter_next (&iter, NULL, &data)) { + NMConnection *candidate = NM_CONNECTION (data); + + if (strcmp (uuid, nm_connection_get_uuid (candidate)) == 0) + return NM_EXAMPLE_CONNECTION (candidate); + } + return NULL; +} + + +static void +dir_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + NMSystemConfigInterface *config = NM_SYSTEM_CONFIG_INTERFACE (user_data); + SCPluginExample *self = SC_PLUGIN_EXAMPLE (config); + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (self); + char *full_path; + NMExampleConnection *connection; + GError *error = NULL; + + full_path = g_file_get_path (file); + /* XXX: Check here if you need to ignore this file, ie by checking for + * extensions like "~" and ".bak" or ".tmp". If so just return; + */ + + /* Check if we know about this connection already */ + connection = g_hash_table_lookup (priv->connections, full_path); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_DELETED: + if (connection) { + PLUGIN_PRINT (EXAMPLE_PLUGIN_NAME, "removed %s.", full_path); + remove_connection (SC_PLUGIN_EXAMPLE (config), connection, full_path); + } + break; + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + if (connection) { + /* Update of an existing connection. Here we re-read the file and + * compare it against the existing connection to check if anything + * actually changed. + */ + NMExampleConnection *tmp; + + tmp = nm_example_connection_new (full_path, NULL, &error); + if (tmp) { + if (!nm_connection_compare (NM_CONNECTION (connection), + NM_CONNECTION (tmp), + NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS | + NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) { + /* Connection changed; update our internal connection object */ + PLUGIN_PRINT (EXAMPLE_PLUGIN_NAME, "updating %s", full_path); + update_connection_settings (connection, tmp); + } + g_object_unref (tmp); + } else { + /* There was an error parsing the updated connection; it may + * no longer be valid and thus we've got to delete it. If it + * becomes valid again later we'll get another change + * notification, we'll re-read it, and we'll treat it as new. + */ + PLUGIN_PRINT (EXAMPLE_PLUGIN_NAME, " error: %s", + (error && error->message) ? error->message : "(unknown)"); + remove_connection (SC_PLUGIN_EXAMPLE (config), connection, full_path); + } + g_clear_error (&error); + } else { + PLUGIN_PRINT (EXAMPLE_PLUGIN_NAME, "updating %s", full_path); + + /* We don't know about the connection yet, so the change represents + * a completely new connection. + */ + connection = nm_example_connection_new (full_path, NULL, &error); + if (connection) { + NMExampleConnection *found = NULL; + + /* Connection renames will show up as different files but with + * the same UUID. Try to find the original connection. + * A connection rename is treated just like an update except + * there's a bit more housekeeping with the hash table. + */ + found = find_by_uuid (self, nm_connection_get_uuid (NM_CONNECTION (connection))); + if (found) { + const char *old_path = nm_example_connection_get_path (connection); + + /* Removing from the hash table should drop the last reference, + * but of course we want to keep the connection around. + */ + g_object_ref (found); + g_hash_table_remove (priv->connections, old_path); + + /* Updating settings should update the NMExampleConnection's + * filename property too. + */ + update_connection_settings (found, connection); + + /* Re-insert the connection back into the hash with the new filename */ + g_hash_table_insert (priv->connections, + (gpointer) nm_example_connection_get_path (found), + found); + + /* Get rid of the temporary connection */ + g_object_unref (connection); + } else { + /* Completely new connection, not a rename. */ + g_hash_table_insert (priv->connections, + (gpointer) nm_example_connection_get_path (connection), + connection); + /* Tell NM we found a new connection */ + g_signal_emit_by_name (config, NM_SYSTEM_CONFIG_INTERFACE_CONNECTION_ADDED, connection); + } + } else { + PLUGIN_PRINT (EXAMPLE_PLUGIN_NAME, " error: %s", + (error && error->message) ? error->message : "(unknown)"); + g_clear_error (&error); + } + } + break; + default: + break; + } + + g_free (full_path); +} + +static void +conf_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer data) +{ + SCPluginExample *self = SC_PLUGIN_EXAMPLE (data); + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (self); + char *tmp; + + switch (event_type) { + case G_FILE_MONITOR_EVENT_DELETED: + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + /* Unmanaged devices option may have changed; just emit the changed + * signal for unmanaged specs and when NM calls back in to get the + * updated specs we'll re-read the config file then. + */ + g_signal_emit_by_name (self, NM_SYSTEM_CONFIG_INTERFACE_UNMANAGED_SPECS_CHANGED); + + /* Hostname may also have changed; read it and if it did actually + * change, notify NM. + */ + tmp = plugin_get_hostname (self); + if (g_strcmp0 (tmp, priv->hostname) != 0) { + g_free (priv->hostname); + priv->hostname = tmp; + tmp = NULL; + g_object_notify (G_OBJECT (self), NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME); + } + g_free (tmp); + break; + default: + break; + } +} + +/* This function starts the inotify monitors that watch the plugin's config + * file directory for new connections and changes to existing connections. + * At this time all plugins are expected to make NM aware of changes on-the-fly + * instead of requiring a SIGHUP or SIGUSR1 or some D-Bus method to say + * "reload". + */ +static void +setup_monitoring (NMSystemConfigInterface *config) +{ + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (config); + GFileMonitor *monitor; + GFile *file; + + /* Initialize connection hash here; we use the connection hash as the + * "are we initialized yet" variable. + */ + priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); + + /* Set up the watch for our config directory */ + file = g_file_new_for_path (EXAMPLE_DIR); + monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + if (monitor) { + /* This registers the dir_changed() function to be called whenever + * the GFileMonitor object notices a change in the directory. + */ + priv->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (dir_changed), config); + priv->monitor = monitor; + } + + /* Set up a watch on our configuration file, basically just for watching + * whether the user has changed the unmanaged devices option or the + * persistent hostname. + */ + if (priv->conf_file) { + file = g_file_new_for_path (priv->conf_file); + monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + + if (monitor) { + priv->conf_file_monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (conf_file_changed), config); + priv->conf_file_monitor = monitor; + } + } +} + +/*******************************************************************/ + +/* Return to NM the full list of connections this plugin owns */ +static GSList * +get_connections (NMSystemConfigInterface *config) +{ + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (config); + GHashTableIter iter; + NMSettingsConnection *connection; + GSList *list = NULL; + + if (!priv->connections) { + /* If we haven't read connections in yet, do so now */ + setup_monitoring (config); + read_connections (config); + } + + /* Add each connection from our internal hash table to a list returned + * to NetworkManager. + */ + g_hash_table_iter_init (&iter, priv->connections); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &connection)) + list = g_slist_prepend (list, connection); + return list; +} + +/* Called by NetworkManager when a user adds a new connection via D-Bus. + * The plugin should convert the data in 'connection' to its on-disk format + * write it out to disk, and return an object that's a subclass of + * NMSettingsConnection. Typically plugins will subclass NMSettingsConnection + * and use that class to handle any plugin-specific stuff like monitoring + * the on-disk config files for changes, and/or parsing the file-format and + * converting back and forth from that to NMConnection. + */ +static NMSettingsConnection * +add_connection (NMSystemConfigInterface *config, + NMConnection *connection, + GError **error) +{ + SCPluginExample *self = SC_PLUGIN_EXAMPLE (config); + NMSettingsConnection *added = NULL; + char *path = NULL; + + /* Write it out first, then add the connection to our internal list; that + * way we don't trigger the new NMSettingsConnection subclass' file watch + * functions needlessly. + */ + if (write_connection (connection, NULL, &path, error)) { + added = _internal_new_connection (self, path, connection, error); + g_free (path); + } + return added; +} + +/* This function returns a list of "unmanaged device specs" which represent + * a list of devices that NetworkManager should not manage. Each unmanaged + * spec item has a specific format starting with a "tag" and followed by + * tag-specific data. The only currently specified item is "mac:" followed + * by the MAC address of the interface NM should not manage. This function + * reads the list of unmanaged devices from wherever the plugin wants to + * store them and returns that list to NetworkManager. + */ +static GSList * +get_unmanaged_specs (NMSystemConfigInterface *config) +{ + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (config); + GKeyFile *key_file; + GSList *specs = NULL; + GError *error = NULL; + char *str, **macs; + int i; + + if (!priv->conf_file) + return NULL; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, priv->conf_file, G_KEY_FILE_NONE, &error)) { + g_warning ("Error parsing file '%s': %s", priv->conf_file, error->message); + g_error_free (error); + goto out; + } + + + str = g_key_file_get_value (key_file, "keyfile", "unmanaged-devices", NULL); + if (!str) + goto out; + + macs = g_strsplit (str, ";", -1); + for (i = 0; macs[i] != NULL; i++) { + /* Verify unmanaged specification and add it to the list */ + if (strlen (macs[i]) > 4 && !strncmp (macs[i], "mac:", 4) && ether_aton (macs[i] + 4)) { + char *p = macs[i]; + + /* To accept uppercase MACs in configuration file, we have to + * convert values to lowercase here. Unmanaged MACs in specs are + * always in lowercase. + */ + while (*p) { + *p = g_ascii_tolower (*p); + p++; + } + + specs = g_slist_append (specs, macs[i]); + } else { + g_warning ("Error in file '%s': invalid unmanaged-devices entry: '%s'", priv->conf_file, macs[i]); + g_free (macs[i]); + } + } + + g_free (macs); /* Yes, g_free, not g_strfreev because we need the strings in the list */ + g_free (str); + +out: + g_key_file_free (key_file); + return specs; +} + + +static char * +plugin_get_hostname (SCPluginExample *plugin) +{ + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (plugin); + GKeyFile *key_file; + char *hostname = NULL; + GError *error = NULL; + + if (!priv->conf_file) + return NULL; + + /* Read the persistent hostname out of backing storage, which happens + * to be the NM config file. Other plugins (like distro-specific ones) + * should read it from the distro-specific location like /etc/hostname. + */ + key_file = g_key_file_new (); + if (g_key_file_load_from_file (key_file, priv->conf_file, G_KEY_FILE_NONE, &error)) + hostname = g_key_file_get_value (key_file, "keyfile", "hostname", NULL); + else { + g_warning ("Error parsing file '%s': %s", priv->conf_file, error->message); + g_error_free (error); + } + + g_key_file_free (key_file); + return hostname; +} + +static gboolean +plugin_set_hostname (SCPluginExample *plugin, const char *hostname) +{ + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (plugin); + GKeyFile *key_file; + GError *error = NULL; + gboolean success = FALSE; + char *data; + gsize len; + + if (!priv->conf_file) { + g_warning ("Error saving hostname: no config file"); + return FALSE; + } + + /* This just saves the hostname to the NM config file in a section + * private to this plugin. + */ + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, priv->conf_file, G_KEY_FILE_NONE, &error)) { + g_warning ("Error parsing file '%s': %s", priv->conf_file, error->message); + g_error_free (error); + goto out; + } + + g_key_file_set_string (key_file, "example", "hostname", hostname); + + data = g_key_file_to_data (key_file, &len, &error); + if (data) { + /* Save updated file to disk */ + g_file_set_contents (priv->conf_file, data, len, &error); + g_free (data); + + /* Update internal copy of hostname */ + g_free (priv->hostname); + priv->hostname = g_strdup (hostname); + success = TRUE; + } + + if (error) { + g_warning ("Error saving hostname: %s", error->message); + g_error_free (error); + } + +out: + g_key_file_free (key_file); + return success; +} + +/* GObject */ + +static void +sc_plugin_example_init (SCPluginExample *plugin) +{ + /* Here we'd do any instance-specific initialization like setting + * members of SCPluginExamplePrivate to default values. But we + * don't have anything to do here since most initialization is done + * when NM calls the various entry points. + */ +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case NM_SYSTEM_CONFIG_INTERFACE_PROP_NAME: + g_value_set_string (value, EXAMPLE_PLUGIN_NAME); + break; + case NM_SYSTEM_CONFIG_INTERFACE_PROP_INFO: + g_value_set_string (value, EXAMPLE_PLUGIN_INFO); + break; + case NM_SYSTEM_CONFIG_INTERFACE_PROP_CAPABILITIES: + /* Return capabilities to NM; this plugin supports changing connections + * as well as being capable of saving the hostname to persistent storage. + * If the plugin can't write out updated configuration, then obviously + * it shouldn't advertise that capability. If it can't save hostnames + * to persistent storage, it shouldn't advertise that capability either. + */ + g_value_set_uint (value, NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_CONNECTIONS | + NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME); + break; + case NM_SYSTEM_CONFIG_INTERFACE_PROP_HOSTNAME: + /* Return the hostname we've read from persistent storage */ + g_value_set_string (value, SC_PLUGIN_EXAMPLE_GET_PRIVATE (object)->hostname); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + const char *hostname; + + switch (prop_id) { + case NM_SYSTEM_CONFIG_INTERFACE_PROP_HOSTNAME: + /* We'll get here when the user has changed the hostname via NM's + * D-Bus interface and we're requested to save this hostname to + * persistent storage. + */ + hostname = g_value_get_string (value); + if (hostname && strlen (hostname) < 1) + hostname = NULL; + plugin_set_hostname (SC_PLUGIN_EXAMPLE (object), hostname); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + SCPluginExamplePrivate *priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (object); + + /* GObject has a two-stage object destruction process: dispose and finalize. + * In dispose the object should free any references it might have on other + * objects to break circular refs, then it's finally cleaned up by finalize. + * We don't bother to implement a finalize, so we just make sure that we + * clean everything up (including clearing pointers) in dispose so that + * if GObject decides to revive this object post-dispose (yes, legal) + * we don't crash on dangling pointers. + */ + + if (priv->monitor) { + if (priv->monitor_id) { + g_signal_handler_disconnect (priv->monitor, priv->monitor_id); + priv->monitor_id = 0; + } + + g_file_monitor_cancel (priv->monitor); + g_object_unref (priv->monitor); + priv->monitor = NULL; + } + + if (priv->conf_file_monitor) { + if (priv->conf_file_monitor_id) { + g_signal_handler_disconnect (priv->conf_file_monitor, priv->conf_file_monitor_id); + priv->conf_file_monitor_id = 0; + } + + g_file_monitor_cancel (priv->conf_file_monitor); + g_object_unref (priv->conf_file_monitor); + priv->conf_file_monitor = NULL; + } + + if (priv->connections) { + /* Destroying the connections hash unrefs each connection in it + * due to the GHashTable value_destroy_func that we passed into + * g_hash_table_new_full(). + */ + g_hash_table_destroy (priv->connections); + priv->connections = NULL; + } + + g_free (priv->hostname); + priv->hostname = NULL; + g_free (priv->conf_file); + priv->conf_file = NULL; + + /* Chain up to the superclass */ + G_OBJECT_CLASS (sc_plugin_example_parent_class)->dispose (object); +} + +/* This function gets called to set up any method and property overrides + * of superclasses, and also (if we actually had any) to set up any + * custom properties and signals this object might have. This is called before + * the object is actually instantiated; it just sets up the generic class + * stuff, not anything related to a specific object instance. + */ +static void +sc_plugin_example_class_init (SCPluginExampleClass *req_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (req_class); + + /* This actually creates and zeros the object's instance data struct */ + g_type_class_add_private (req_class, sizeof (SCPluginExamplePrivate)); + + /* Override GObject base class methods with our custom implementations */ + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + /* Override various GObject properties that we need to change. Here we + * just tell GObject that we will be handling the get/set operations for + * these specific properties. They are actually defined by the + * NMSystemConfigInterface interface in nm-system-config-interface.c. + * What happens here is that we tell GObject that for a given property + * name (ie NM_SYSTEM_CONFIG_INTERFACE_NAME) we'll be using the enum value + * NM_SYSTEM_CONFIG_INTERFACE_PROP_NAME locally in get_property() and + * set_property(). + */ + g_object_class_override_property (object_class, + NM_SYSTEM_CONFIG_INTERFACE_PROP_NAME, + NM_SYSTEM_CONFIG_INTERFACE_NAME); + + g_object_class_override_property (object_class, + NM_SYSTEM_CONFIG_INTERFACE_PROP_INFO, + NM_SYSTEM_CONFIG_INTERFACE_INFO); + + g_object_class_override_property (object_class, + NM_SYSTEM_CONFIG_INTERFACE_PROP_CAPABILITIES, + NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES); + + g_object_class_override_property (object_class, + NM_SYSTEM_CONFIG_INTERFACE_PROP_HOSTNAME, + NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME); +} + +static void +system_config_interface_init (NMSystemConfigInterface *sci_intf) +{ + /* Interface implementation for NMSystemConfigInterface. This sets + * up the GInterface vtable that lets GObject know what functions to + * call for each method of the NMSystemConfigInterface interface. + */ + sci_intf->get_connections = get_connections; + sci_intf->add_connection = add_connection; + sci_intf->get_unmanaged_specs = get_unmanaged_specs; +} + +/*******************************************************************/ + +/* Factory function: this is the first entry point for NetworkManager, which + * gets called during NM startup to create the the instance of this plugin + * that NetworkManager will actually use. Since every plugin is a singleton + * we just return a singleton instance. This function should never be called + * twice. + */ +G_MODULE_EXPORT GObject * +nm_system_config_factory (const char *config_file) +{ + static SCPluginExample *singleton = NULL; + SCPluginExamplePrivate *priv; + + if (!singleton) { + /* Instantiate our plugin */ + singleton = SC_PLUGIN_EXAMPLE (g_object_new (SC_TYPE_PLUGIN_EXAMPLE, NULL)); + if (singleton) { + priv = SC_PLUGIN_EXAMPLE_GET_PRIVATE (singleton); + + /* Cache the config file path */ + priv->conf_file = g_strdup (config_file); + } + } else { + /* This function should never be called twice */ + g_assert_not_reached (); + } + + return G_OBJECT (singleton); +} + diff --git a/src/settings/plugins/example/plugin.h b/src/settings/plugins/example/plugin.h new file mode 100644 index 0000000000..23b68e2e28 --- /dev/null +++ b/src/settings/plugins/example/plugin.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - example plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +#include + +/* GObject boilerplate: you usually only need to rename 'example' here to + * your plugin's name. These functions get used when casting pointers + * to your plugin's object type. + */ +#define SC_TYPE_PLUGIN_EXAMPLE (sc_plugin_example_get_type ()) +#define SC_PLUGIN_EXAMPLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SC_TYPE_PLUGIN_EXAMPLE, SCPluginExample)) +#define SC_PLUGIN_EXAMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SC_TYPE_PLUGIN_EXAMPLE, SCPluginExampleClass)) +#define SC_IS_PLUGIN_EXAMPLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SC_TYPE_PLUGIN_EXAMPLE)) +#define SC_IS_PLUGIN_EXAMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SC_TYPE_PLUGIN_EXAMPLE)) +#define SC_PLUGIN_EXAMPLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SC_TYPE_PLUGIN_EXAMPLE, SCPluginExampleClass)) + +typedef struct { + /* GObject instance structure for the plugin; we don't do anything special + * here so this object's instance is exactly the same as its parent. + */ + GObject parent; +} SCPluginExample; + +typedef struct { + /* GObject class structure; we don't do anything special here + * so this object's class is exactly the same as its parent. Typically + * if the plugin implemented custom signals their prototypes would go + * here, but most plugins don't need to do this. + */ + GObjectClass parent; +} SCPluginExampleClass; + +GType sc_plugin_example_get_type (void); + +#endif /* _PLUGIN_H_ */ diff --git a/src/settings/plugins/example/reader.c b/src/settings/plugins/example/reader.c new file mode 100644 index 0000000000..9a4cecaa1a --- /dev/null +++ b/src/settings/plugins/example/reader.c @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2012 Red Hat, Inc. + */ + +#include +#include + +#include +#include + +#include "common.h" + +NMConnection * +connection_from_file (const char *filename, GError **error) +{ + /* This function should, given a file path, read that file and convert its + * data into an NMConnection. There are two approaches to this. First, + * if the plugin data format is similar to the NMConnection internal + * format, you can get away with calling nm_connection_for_each_setting_value() + * to iterate through every possible setting's keys and read that value + * from the plugin's format. If the plugin's format is siginificantly + * different then you may have to build up the connection manually by + * determining the type of connection from the on-disk data, creating + * each setting object, adding the values, then adding that setting to + * the NMConnection. + */ + + return NULL; +} + diff --git a/src/settings/plugins/example/writer.c b/src/settings/plugins/example/writer.c new file mode 100644 index 0000000000..4703fa9482 --- /dev/null +++ b/src/settings/plugins/example/writer.c @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2012 Red Hat, Inc. + */ + +#include +#include + +#include + +#include "common.h" + +gboolean +write_connection (NMConnection *connection, + const char *existing_path, + char **out_path, + GError **error) +{ + /* This function should take the NMConnection and convert it to the format + * which this plugin uses on-disk and then write out that data. It returns + * the file path of the file that represents this connection data so that + * the plugin can track it for change notification and updates. If + * 'existing_path' is given we can assume that this is an update of an + * existing connection and not a completely new one. + */ + + /* There are two approaches to converting the data. The first more manual + * approach consists of grabbing each setting value from the NMConnection + * and converting it into the appropriate value for the plugin's data + * format. This is usually taken by distro plugins becuase their format + * is significantly different than NetworkManager's internal format. + * The second uses nm_connection_for_each_setting_value() to iterate + * through each value of each setting in the NMConnection, convert it to + * the required format, and write it out, but this requires that the + * plugin format more closely follow the NetworkManager internal format. + */ + + return FALSE; +} +