/* -*- mode: c; style: linux -*- */ /* location.c * Copyright (C) 2000-2001 Ximian, Inc. * * Written by Bradford Hovinen (hovinen@ximian.com) * * 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, 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., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "location.h" #include "archive.h" #include "util.h" #include "archiver-client.h" static BonoboXObjectClass *parent_class; enum { ARG_0, ARG_LOCID, ARG_LABEL, ARG_ARCHIVE, ARG_INHERITS }; /* Note about backend containment in this location */ typedef struct _BackendNote BackendNote; struct _BackendNote { gchar *backend_id; ContainmentType type; }; struct _LocationPrivate { Archive *archive; gchar *locid; gchar *fullpath; gchar *label; Location *parent; GList *contains_list; /* List of BackendNotes */ gboolean is_new; gboolean contains_list_dirty; gboolean deleted; ConfigLog *config_log; BonoboEventSource *es; }; #define LOCATION_FROM_SERVANT(servant) (LOCATION (bonobo_object_from_servant (servant))) static void location_init (Location *location); static void location_class_init (LocationClass *klass); static void location_set_arg (GtkObject *object, GtkArg *arg, guint arg_id); static void location_get_arg (GtkObject *object, GtkArg *arg, guint arg_id); static void location_destroy (GtkObject *object); static void location_finalize (GtkObject *object); static gboolean location_do_rollback (Location *location, gchar *backend_id, xmlDocPtr xml_doc); static gint get_backends_cb (BackendList *backend_list, gchar *backend_id, Location *location); static gboolean do_create (Location *location); static gboolean do_load (Location *location); static gboolean load_metadata_file (Location *location, char *filename, gboolean is_default); static void save_metadata (Location *location); static void write_metadata_file (Location *location, gchar *filename); static gint run_backend_proc (gchar *backend_id, gboolean do_get, pid_t *pid); static BackendNote *backend_note_new (const gchar *backend_id, ContainmentType type); static void backend_note_destroy (BackendNote *note); static const BackendNote *find_note (Location *location, const gchar *backend_id); static GList *create_backends_list (Location *location1, Location *location2); static GList *merge_backend_lists (GList *backends1, GList *backends2); /* CORBA interface methods */ static CORBA_char * impl_ConfigArchiver_Location_getStorageFilename (PortableServer_Servant servant, const CORBA_char *backendId, CORBA_boolean isDefaultData, CORBA_Environment *ev) { gchar *filename; CORBA_char *ret; filename = location_get_storage_filename (LOCATION_FROM_SERVANT (servant), backendId, isDefaultData); ret = CORBA_string_dup (filename); g_free (filename); return ret; } static CORBA_char * impl_ConfigArchiver_Location_getRollbackFilename (PortableServer_Servant servant, ConfigArchiver_Time timep, CORBA_long steps, const CORBA_char *backendId, CORBA_boolean parentChain, CORBA_Environment *ev) { gchar *filename; CORBA_char *ret; struct tm timeb, *timeb_p = &timeb; if (timep != 0) gmtime_r ((time_t *) &timep, timeb_p); else timeb_p = NULL; filename = location_get_rollback_filename (LOCATION_FROM_SERVANT (servant), timeb_p, steps, backendId, parentChain); if (filename != NULL) { ret = CORBA_string_dup (filename); g_free (filename); } else { ret = NULL; bonobo_exception_set (ev, ex_ConfigArchiver_Location_RollbackDataNotFound); } return ret; } static void impl_ConfigArchiver_Location_storageComplete (PortableServer_Servant servant, const CORBA_char *filename, CORBA_Environment *ev) { location_storage_complete (LOCATION_FROM_SERVANT (servant), filename); } static void impl_ConfigArchiver_Location_rollbackBackends (PortableServer_Servant servant, ConfigArchiver_Time timep, CORBA_long steps, const ConfigArchiver_StringSeq *backends, CORBA_boolean parentChain, CORBA_Environment *ev) { GList *node = NULL; unsigned int i; struct tm timeb, *timeb_p = &timeb; for (i = 0; i < backends->_length; i++) node = g_list_prepend (node, backends->_buffer[i]); if (timep != 0) gmtime_r ((time_t *) &timep, &timeb); else timeb_p = NULL; location_rollback_backends_to (LOCATION_FROM_SERVANT (servant), timeb_p, steps, node, parentChain); g_list_free (node); } static ConfigArchiver_Time impl_ConfigArchiver_Location_getModificationTime (PortableServer_Servant servant, const CORBA_char *backendId, CORBA_Environment *ev) { const struct tm *time_s; struct tm *tmp_date; ConfigArchiver_Time ret; time_s = location_get_modification_time (LOCATION_FROM_SERVANT (servant), backendId); if (time_s != NULL) { tmp_date = dup_date (time_s); ret = mktime (tmp_date); g_free (tmp_date); } else { ret = 0; } return ret; } static ConfigArchiver_ContainmentType impl_ConfigArchiver_Location_contains (PortableServer_Servant servant, const CORBA_char *backendId, CORBA_Environment *ev) { return location_contains (LOCATION_FROM_SERVANT (servant), backendId); } static CORBA_long impl_ConfigArchiver_Location_addBackend (PortableServer_Servant servant, const CORBA_char *backendId, ConfigArchiver_ContainmentType containmentType, CORBA_Environment *ev) { return location_add_backend (LOCATION_FROM_SERVANT (servant), backendId, containmentType); } static void impl_ConfigArchiver_Location_removeBackend (PortableServer_Servant servant, const CORBA_char *backendId, CORBA_Environment *ev) { location_remove_backend (LOCATION_FROM_SERVANT (servant), backendId); } static CORBA_boolean impl_ConfigArchiver_Location_doesBackendChange (PortableServer_Servant servant, ConfigArchiver_Location location, const CORBA_char *backendId, CORBA_Environment *ev) { Location *loc1, *loc2; loc1 = LOCATION_FROM_SERVANT (servant); loc2 = LOCATION_FROM_SERVANT (location->servant); return location_does_backend_change (loc1, loc2, backendId); } static void impl_ConfigArchiver_Location_garbageCollect (PortableServer_Servant servant, CORBA_Environment *ev) { location_garbage_collect (LOCATION_FROM_SERVANT (servant)); } static void impl_ConfigArchiver_Location_delete (PortableServer_Servant servant, CORBA_Environment *ev) { location_delete (LOCATION_FROM_SERVANT (servant)); } static ConfigArchiver_Location impl_ConfigArchiver_Location__get_parent (PortableServer_Servant servant, CORBA_Environment *ev) { Location *location = LOCATION_FROM_SERVANT (servant); if (location->p->parent != NULL) return bonobo_object_dup_ref (BONOBO_OBJREF (location->p->parent), ev); else return CORBA_OBJECT_NIL; } static CORBA_char * impl_ConfigArchiver_Location__get_path (PortableServer_Servant servant, CORBA_Environment *ev) { return CORBA_string_dup (LOCATION_FROM_SERVANT (servant)->p->fullpath); } static ConfigArchiver_StringSeq * impl_ConfigArchiver_Location__get_backendList (PortableServer_Servant servant, CORBA_Environment *ev) { ConfigArchiver_StringSeq *ret; Location *location; GList *node; guint i = 0; location = LOCATION_FROM_SERVANT (servant); ret = ConfigArchiver_StringSeq__alloc (); ret->_length = g_list_length (location->p->contains_list); ret->_buffer = CORBA_sequence_CORBA_string_allocbuf (ret->_length); for (node = location->p->contains_list; node != NULL; node = node->next) ret->_buffer[i++] = CORBA_string_dup (((BackendNote *) node->data)->backend_id); return ret; } static CORBA_char * impl_ConfigArchiver_Location__get_label (PortableServer_Servant servant, CORBA_Environment *ev) { return CORBA_string_dup (LOCATION_FROM_SERVANT (servant)->p->label); } static CORBA_char * impl_ConfigArchiver_Location__get_id (PortableServer_Servant servant, CORBA_Environment *ev) { return CORBA_string_dup (LOCATION_FROM_SERVANT (servant)->p->locid); } static void impl_ConfigArchiver_Location__set_label (PortableServer_Servant servant, const CORBA_char *label, CORBA_Environment *ev) { gtk_object_set (GTK_OBJECT (LOCATION_FROM_SERVANT (servant)), "label", label, NULL); } static void impl_ConfigArchiver_Location__set_id (PortableServer_Servant servant, const CORBA_char *id, CORBA_Environment *ev) { gtk_object_set (GTK_OBJECT (LOCATION_FROM_SERVANT (servant)), "locid", id, NULL); } BONOBO_X_TYPE_FUNC_FULL (Location, ConfigArchiver_Location, BONOBO_X_OBJECT_TYPE, location); static void location_init (Location *location) { location->p = g_new0 (LocationPrivate, 1); location->p->archive = NULL; location->p->locid = NULL; location->p->is_new = FALSE; location->p->contains_list_dirty = FALSE; location->p->es = bonobo_event_source_new (); bonobo_object_add_interface (BONOBO_OBJECT (location), BONOBO_OBJECT (location->p->es)); } static void location_class_init (LocationClass *klass) { GtkObjectClass *object_class; object_class = GTK_OBJECT_CLASS (klass); object_class->destroy = location_destroy; object_class->finalize = location_finalize; object_class->set_arg = location_set_arg; object_class->get_arg = location_get_arg; gtk_object_add_arg_type ("Location::archive", GTK_TYPE_POINTER, GTK_ARG_READWRITE, ARG_ARCHIVE); gtk_object_add_arg_type ("Location::locid", GTK_TYPE_POINTER, GTK_ARG_READWRITE, ARG_LOCID); gtk_object_add_arg_type ("Location::label", GTK_TYPE_POINTER, GTK_ARG_READWRITE, ARG_LABEL); gtk_object_add_arg_type ("Location::inherits", GTK_TYPE_POINTER, GTK_ARG_READWRITE, ARG_INHERITS); klass->do_rollback = location_do_rollback; klass->epv.getStorageFilename = impl_ConfigArchiver_Location_getStorageFilename; klass->epv.getRollbackFilename = impl_ConfigArchiver_Location_getRollbackFilename; klass->epv.storageComplete = impl_ConfigArchiver_Location_storageComplete; klass->epv.rollbackBackends = impl_ConfigArchiver_Location_rollbackBackends; klass->epv.getModificationTime = impl_ConfigArchiver_Location_getModificationTime; klass->epv.contains = impl_ConfigArchiver_Location_contains; klass->epv.addBackend = impl_ConfigArchiver_Location_addBackend; klass->epv.removeBackend = impl_ConfigArchiver_Location_removeBackend; klass->epv.doesBackendChange = impl_ConfigArchiver_Location_doesBackendChange; klass->epv.garbageCollect = impl_ConfigArchiver_Location_garbageCollect; klass->epv.delete = impl_ConfigArchiver_Location_delete; klass->epv._get_parent = impl_ConfigArchiver_Location__get_parent; klass->epv._get_path = impl_ConfigArchiver_Location__get_path; klass->epv._get_backendList = impl_ConfigArchiver_Location__get_backendList; klass->epv._get_label = impl_ConfigArchiver_Location__get_label; klass->epv._get_id = impl_ConfigArchiver_Location__get_id; klass->epv._set_label = impl_ConfigArchiver_Location__set_label; klass->epv._set_id = impl_ConfigArchiver_Location__set_id; parent_class = gtk_type_class (BONOBO_X_OBJECT_TYPE); } static void location_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) { Location *location; g_return_if_fail (object != NULL); g_return_if_fail (IS_LOCATION (object)); g_return_if_fail (arg != NULL); location = LOCATION (object); switch (arg_id) { case ARG_ARCHIVE: g_return_if_fail (GTK_VALUE_POINTER (*arg) != NULL); g_return_if_fail (IS_ARCHIVE (GTK_VALUE_POINTER (*arg))); location->p->archive = GTK_VALUE_POINTER (*arg); bonobo_object_ref (BONOBO_OBJECT (location->p->archive)); break; case ARG_LOCID: if (GTK_VALUE_POINTER (*arg) != NULL) { if (location->p->locid != NULL) g_free (location->p->locid); location->p->locid = g_strdup (GTK_VALUE_POINTER (*arg)); if (!strcmp (location->p->locid, "default") && location->p->label == NULL) location->p->label = g_strdup (_("Default Location")); else if (location->p->label == NULL) location->p->label = g_strdup (location->p->locid); } break; case ARG_LABEL: if (GTK_VALUE_POINTER (*arg) != NULL) { if (location->p->label != NULL) g_free (location->p->label); location->p->label = g_strdup (GTK_VALUE_POINTER (*arg)); } break; case ARG_INHERITS: if (GTK_VALUE_POINTER (*arg) != NULL) { g_return_if_fail (IS_LOCATION (GTK_VALUE_POINTER (*arg))); location->p->parent = GTK_VALUE_POINTER (*arg); bonobo_object_ref (BONOBO_OBJECT (location->p->parent)); } default: break; } } static void location_get_arg (GtkObject *object, GtkArg *arg, guint arg_id) { Location *location; g_return_if_fail (object != NULL); g_return_if_fail (IS_LOCATION (object)); g_return_if_fail (arg != NULL); location = LOCATION (object); switch (arg_id) { case ARG_ARCHIVE: GTK_VALUE_POINTER (*arg) = location->p->archive; break; case ARG_LOCID: GTK_VALUE_POINTER (*arg) = location->p->locid; break; case ARG_LABEL: GTK_VALUE_POINTER (*arg) = location->p->label; break; case ARG_INHERITS: GTK_VALUE_POINTER (*arg) = location->p->parent; break; default: arg->type = GTK_TYPE_INVALID; break; } } /** * location_new: * @archive: * @locid: * @inherits: * * Create a new location with the given name and parent identifier in the * given archive * * Return value: Reference to location **/ BonoboObject * location_new (Archive *archive, const gchar *locid, const gchar *label, Location *inherits) { GtkObject *object; g_return_val_if_fail (archive != NULL, NULL); g_return_val_if_fail (IS_ARCHIVE (archive), NULL); g_return_val_if_fail (locid != NULL, NULL); object = gtk_object_new (location_get_type (), "archive", archive, "locid", locid, "label", label, "inherits", inherits, NULL); if (!do_create (LOCATION (object))) { bonobo_object_unref (BONOBO_OBJECT (object)); return NULL; } LOCATION (object)->p->is_new = TRUE; save_metadata (LOCATION (object)); return BONOBO_OBJECT (object); } /** * location_open: * @archive: * @locid: * * Open the location with the given name from the given archive * * Return value: Reference of location **/ BonoboObject * location_open (Archive *archive, const gchar *locid) { GtkObject *object; g_return_val_if_fail (archive != NULL, NULL); g_return_val_if_fail (IS_ARCHIVE (archive), NULL); g_return_val_if_fail (locid != NULL, NULL); object = gtk_object_new (location_get_type (), "archive", archive, "locid", locid, NULL); if (!do_load (LOCATION (object))) { bonobo_object_unref (BONOBO_OBJECT (object)); return NULL; } return BONOBO_OBJECT (object); } static void location_destroy (GtkObject *object) { Location *location; g_return_if_fail (object != NULL); g_return_if_fail (IS_LOCATION (object)); location = LOCATION (object); DEBUG_MSG ("Enter: %s", location->p->locid); save_metadata (location); if (location->p->config_log) gtk_object_destroy (GTK_OBJECT (location->p->config_log)); if (location->p->parent) bonobo_object_unref (BONOBO_OBJECT (location->p->parent)); archive_unregister_location (location->p->archive, location); bonobo_object_unref (BONOBO_OBJECT (location->p->archive)); GTK_OBJECT_CLASS (parent_class)->destroy (object); } static void location_finalize (GtkObject *object) { Location *location; GList *node; g_return_if_fail (object != NULL); g_return_if_fail (IS_LOCATION (object)); location = LOCATION (object); for (node = location->p->contains_list; node; node = node->next) backend_note_destroy (node->data); g_list_free (location->p->contains_list); if (location->p->fullpath != NULL) g_free (location->p->fullpath); if (location->p->label != NULL) g_free (location->p->label); if (location->p->locid != NULL) g_free (location->p->locid); g_free (location->p); location->p = (LocationPrivate *) 0xdeadbeef; GTK_OBJECT_CLASS (parent_class)->finalize (object); } /* Perform rollback for a given backend and id number. Return TRUE on * success and FALSE otherwise. * * FIXME: Better error reporting */ static gboolean location_do_rollback (Location *location, gchar *backend_id, xmlDocPtr doc) { int fd, status; FILE *output; pid_t pid; g_return_val_if_fail (location != NULL, FALSE); g_return_val_if_fail (IS_LOCATION (location), FALSE); g_return_val_if_fail (backend_id != NULL, FALSE); g_return_val_if_fail (doc != NULL, FALSE); /* FIXME: Some mechanism for retrieving the factory defaults settings * would be useful here */ if (doc == NULL) return FALSE; fd = run_backend_proc (backend_id, FALSE, &pid); if (fd == -1) return FALSE; output = fdopen (fd, "w"); xmlDocDump (output, doc); if (fflush (output) == EOF) g_critical ("%s: Could not dump buffer: %s", G_GNUC_FUNCTION, g_strerror (errno)); if (fclose (output) == EOF) { g_critical ("%s: Could not close output stream: %s", G_GNUC_FUNCTION, g_strerror (errno)); return FALSE; } waitpid (pid, &status, 0); return TRUE; } static gint data_delete_cb (ConfigLog *config_log, gint id, gchar *backend_id, struct tm *date, Location *location) { gchar *filename; filename = g_strdup_printf ("%s/%08x.xml", location->p->fullpath, id); unlink (filename); g_free (filename); return 0; } void location_delete (Location *location) { gchar *metadata_filename; g_return_if_fail (location != NULL); g_return_if_fail (IS_LOCATION (location)); metadata_filename = g_strconcat (location->p->fullpath, "/location.xml", NULL); unlink (metadata_filename); g_free (metadata_filename); config_log_iterate (location->p->config_log, (ConfigLogIteratorCB) data_delete_cb, location); config_log_delete (location->p->config_log); if (rmdir (location->p->fullpath) == -1) g_warning ("%s: Could not remove directory: %s\n", G_GNUC_FUNCTION, g_strerror (errno)); location->p->deleted = TRUE; } /** * location_rollback_backends_to: * @location: * @date: * @backends: * @parent_chain: * * Roll back the list of backends specified to the given date, optionally * chaining to the parent location. This destroys the list of backends given * * FIXME: To enforce some kind of ordering on backend application, just create * a comparison function between backend ids that calls a BackendList method * to see which item should go first, and then call g_list_sort with that on * the backend list **/ void location_rollback_backends_to (Location *location, struct tm *date, gint steps, GList *backends, gboolean parent_chain) { GList *node; gchar *backend_id, *filename; xmlDocPtr doc; g_return_if_fail (location != NULL); g_return_if_fail (IS_LOCATION (location)); g_return_if_fail (location->p->config_log != NULL); g_return_if_fail (IS_CONFIG_LOG (location->p->config_log)); for (node = backends; node; node = node->next) { backend_id = node->data; filename = location_get_rollback_filename (location, date, steps, backend_id, parent_chain); if (filename == NULL) continue; doc = xmlParseFile (filename); g_free (filename); if (doc == NULL) continue; LOCATION_CLASS (GTK_OBJECT (location)->klass)->do_rollback (location, backend_id, doc); xmlFreeDoc (doc); } } /** * location_get_storage_filename: * @location: * @backend_id: * * Finds a new storage filename for storing rollback data from the backend id **/ gchar * location_get_storage_filename (Location *location, const gchar *backend_id, gboolean is_default) { guint id; g_return_val_if_fail (location != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location), NULL); g_return_val_if_fail (backend_id != NULL, NULL); id = config_log_write_entry (location->p->config_log, backend_id, is_default); return g_strdup_printf ("%s/%08x.xml", location->p->fullpath, id); } /** * location_get_rollback_filename: * @location: * @date: * @steps: * @backend_id: * @parent_chain: * * Get the filename for the rollback data for the date or number of steps, and * backend id given. String should be freed after use. Returns NULL if no data * were found. **/ gchar * location_get_rollback_filename (Location *location, struct tm *date, gint steps, const gchar *backend_id, gboolean parent_chain) { gint id; g_return_val_if_fail (location != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location), NULL); g_return_val_if_fail (location->p->config_log != NULL, NULL); g_return_val_if_fail (IS_CONFIG_LOG (location->p->config_log), NULL); if (steps > 0) id = config_log_get_rollback_id_by_steps (location->p->config_log, steps, backend_id); else id = config_log_get_rollback_id_for_date (location->p->config_log, date, backend_id); if (id != -1) return g_strdup_printf ("%s/%08x.xml", location->p->fullpath, id); else if (parent_chain && location->p->parent != NULL) return location_get_rollback_filename (location->p->parent, date, steps, backend_id, parent_chain); else return NULL; } /** * location_storage_complete: * @location: * @filename: * * Notify the location object that storage of the rollback data at the given * filename is complete **/ void location_storage_complete (Location *location, const gchar *filename) { const gchar *tmp; const gchar *backend_id; guint id; BonoboArg *value; tmp = strrchr (filename, '/'); if (tmp == NULL) return; sscanf (tmp + 1, "%x", &id); backend_id = config_log_get_backend_id_for_id (location->p->config_log, id); value = bonobo_arg_new (BONOBO_ARG_STRING); BONOBO_ARG_SET_STRING (value, backend_id); bonobo_event_source_notify_listeners (location->p->es, "ConfigArchiver/Location:newRollbackData", value, NULL); bonobo_arg_release (value); } /** * location_store: * @location: * @backend_id: * @input: * @store_type: STORE_FULL means blindly store the data, without * modification. STORE_COMPARE_PARENT means subtract the settings the parent * has that are different and store the result. STORE_MASK_PREVIOUS means * store only those settings that are reflected in the previous logged data; * if there do not exist such data, act as in * STORE_COMPARE_PARENT. STORE_DEFAULT means these are default data. * * Store configuration data from the given stream in the location under the * given backend id * * Return value: 0 on success, -1 if it cannot parse the XML, and -2 if no * data were supplied **/ gint location_store (Location *location, gchar *backend_id, FILE *input, StoreType store_type) { xmlDocPtr doc; char buffer[2048]; int t = 0; GString *doc_str; g_return_val_if_fail (location != NULL, -2); g_return_val_if_fail (IS_LOCATION (location), -2); fflush (input); fcntl (fileno (input), F_SETFL, 0); doc_str = g_string_new (""); while ((t = read (fileno (input), buffer, sizeof (buffer) - 1)) != 0) { buffer[t] = '\0'; g_string_append (doc_str, buffer); } if (doc_str->len > 0) { doc = xmlParseDoc (doc_str->str); if (doc == NULL) { g_string_free (doc_str, TRUE); return -1; } location_store_xml (location, backend_id, doc, store_type); xmlFreeDoc (doc); } else { return -2; } g_string_free (doc_str, TRUE); return 0; } /** * location_store_xml: * @location: * @backend_id: * @input: * @store_type: STORE_FULL means blindly store the data, without * modification. STORE_COMPARE_PARENT means subtract the settings the parent * has that are different and store the result. STORE_MASK_PREVIOUS means * store only those settings that are reflected in the previous logged data; * if there do not exist such data, act as in STORE_COMPARE_PARENT * * Store configuration data from the given XML document object in the location * under the given backend id **/ void location_store_xml (Location *location, gchar *backend_id, xmlDocPtr xml_doc, ConfigArchiver_StoreType store_type) { CORBA_Environment ev; g_return_if_fail (location != NULL); g_return_if_fail (IS_LOCATION (location)); g_return_if_fail (xml_doc != NULL); CORBA_exception_init (&ev); location_client_store_xml (BONOBO_OBJREF (location), backend_id, xml_doc, store_type, &ev); CORBA_exception_free (&ev); } /** * location_get_modification_time: * @location: * @backend_id: * * Get the time the particular backend was last modified (archived) **/ const struct tm * location_get_modification_time (Location *location, const gchar *backend_id) { gint id; id = config_log_get_rollback_id_by_steps (location->p->config_log, 0, backend_id); if (id < 0) return NULL; else return config_log_get_date_for_id (location->p->config_log, id); } /** * location_contains: * @location: * @backend_id: * * Determine if a location specifies configuration for the given backend * * Return value: Containment type, as defined in the enum **/ ContainmentType location_contains (Location *location, const gchar *backend_id) { BackendList *list; g_return_val_if_fail (location != NULL, FALSE); g_return_val_if_fail (IS_LOCATION (location), FALSE); if (location->p->parent == NULL) { list = archive_get_backend_list (location->p->archive); if (backend_list_contains (list, backend_id)) return ConfigArchiver_CONTAIN_FULL; else return ConfigArchiver_CONTAIN_NONE; } else { const BackendNote *note = find_note (location, backend_id); if (note != NULL) return note->type; else return ConfigArchiver_CONTAIN_NONE; } } /** * location_add_backend: * @location: * @backend_id: * * Adds a backend id to the location's metadata, so that the location * specifies configuration for that backend * * Returns: 0 on success, -1 if the location is toplevel, -2 if the backend is * not registered with the master list, -3 if the type specified was "NONE" **/ gint location_add_backend (Location *location, const gchar *backend_id, ContainmentType type) { g_return_val_if_fail (location != NULL, -1); g_return_val_if_fail (IS_LOCATION (location), -1); g_return_val_if_fail (backend_id != NULL, -2); if (location->p->parent == NULL) return -1; if (type == ConfigArchiver_CONTAIN_NONE) return -3; if (!backend_list_contains (archive_get_backend_list (location->p->archive), backend_id)) return -2; location->p->contains_list = g_list_append (location->p->contains_list, backend_note_new (backend_id, type)); location->p->contains_list_dirty = TRUE; save_metadata (location); return 0; } /** * location_remove_backend: * @location: * @backend_id: * * Removes a backend id from the location's metadata, so that the location no * longer specifies configuration for that backend **/ void location_remove_backend (Location *location, const gchar *backend_id) { GList *node; BackendNote *note; g_return_if_fail (location != NULL); g_return_if_fail (IS_LOCATION (location)); g_return_if_fail (backend_id != NULL); if (location->p->parent == NULL) return; for (node = location->p->contains_list; node; node = node->next) { note = node->data; if (!strcmp (note->backend_id, backend_id)) { backend_note_destroy (note); location->p->contains_list = g_list_remove_link (location->p->contains_list, node); location->p->contains_list_dirty = TRUE; return; } } save_metadata (location); } /** * location_find_path_from_common_parent: * @location: object * @location1: The location with which to find the common parent with the * object * * Finds the common parent between the two locations given and returns a GList * of locations starting at the common parent (starts with NULL if there is no * common parent) and leading to the location object on which this method is * invoked * * Return value: A GList of locations **/ GList * location_find_path_from_common_parent (Location *location, Location *location1) { GList *list_node; gint depth = 0, depth1 = 0; Location *tmp; g_return_val_if_fail (location != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location), NULL); g_return_val_if_fail (location1 != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location1), NULL); list_node = g_list_append (NULL, location); for (tmp = location; tmp; tmp = tmp->p->parent) depth++; for (tmp = location1; tmp; tmp = tmp->p->parent) depth1++; while (depth > depth1) { location = location->p->parent; list_node = g_list_prepend (list_node, location); depth--; } while (depth1 > depth) { location1 = location1->p->parent; depth1--; } while (location && location1 && location != location1) { location = location->p->parent; location1 = location1->p->parent; list_node = g_list_prepend (list_node, location); } return list_node; } /** * location_get_parent * @location: * * Returns a reference to the location that this location inherits * * Return value: Reference to parent location **/ Location * location_get_parent (Location *location) { g_return_val_if_fail (location != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location), NULL); return location->p->parent; } /** * location_get_path: * @location: * * Get the filesystem path to location data * * Return value: String with path; should not be freed **/ const gchar * location_get_path (Location *location) { g_return_val_if_fail (location != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location), NULL); return location->p->fullpath; } /** * location_get_label: * @location: * * Get the human-readable label associated with the location * * Return value: String containing label; should not be freed **/ const gchar * location_get_label (Location *location) { g_return_val_if_fail (location != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location), NULL); return location->p->label; } /** * location_get_id: * @location: * * Get the location id * * Return value: String containing location id; should not be freed **/ const gchar * location_get_id (Location *location) { g_return_val_if_fail (location != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location), NULL); return location->p->locid; } /** * location_foreach_backend: * @location: * @callback: * @data: * * Invokes the function callback on every backend handled by this location **/ void location_foreach_backend (Location *location, LocationBackendCB callback, gpointer data) { GList *node; BackendNote *note; g_return_if_fail (location != NULL); g_return_if_fail (IS_LOCATION (location)); g_return_if_fail (callback != NULL); for (node = location->p->contains_list; node; node = node->next) { note = node->data; if (callback (location, note->backend_id, data)) break; } } /** * location_set_id: * @location: * @name: * * Sets the name (or id) of the location **/ void location_set_id (Location *location, const gchar *locid) { gchar *new_fullpath; g_return_if_fail (location != NULL); g_return_if_fail (IS_LOCATION (location)); g_return_if_fail (locid != NULL); gtk_object_set (GTK_OBJECT (location), "locid", locid, NULL); new_fullpath = g_concat_dir_and_file (archive_get_prefix (location->p->archive), locid); if (rename (location->p->fullpath, new_fullpath) == -1) { g_message ("Could not rename %s to %s: %s", location->p->fullpath, new_fullpath, g_strerror (errno)); } g_free (location->p->fullpath); location->p->fullpath = new_fullpath; config_log_reset_filenames (location->p->config_log); } /** * location_get_changed_backends: * @location: * @location1: * * Get a list of backends that change from location to location1 **/ GList * location_get_changed_backends (Location *location, Location *location1) { GList *backends1, *backends2; g_return_val_if_fail (location != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location), NULL); g_return_val_if_fail (location1 != NULL, NULL); g_return_val_if_fail (IS_LOCATION (location1), NULL); backends1 = create_backends_list (location, location1); backends2 = create_backends_list (location1, location); return merge_backend_lists (backends1, backends2); } /** * location_does_backend_change: * @location: * @location1: * @backend_id: * * Return TRUE if a backend changes when changing from location to location1; * FALSE otherwise **/ gboolean location_does_backend_change (Location *location, Location *location1, const gchar *backend_id) { GList *backends; gboolean ret; gchar *str; g_return_val_if_fail (location != NULL, FALSE); g_return_val_if_fail (IS_LOCATION (location), FALSE); g_return_val_if_fail (location1 != NULL, FALSE); g_return_val_if_fail (IS_LOCATION (location1), FALSE); g_return_val_if_fail (backend_id != NULL, FALSE); backends = location_get_changed_backends (location, location1); str = g_strdup (backend_id); ret = !(g_list_find_custom (backends, str, (GCompareFunc) strcmp) == NULL); g_free (str); g_list_free (backends); return ret; } /* location_get_config_log: * @location: * * Returns the config log object for this location */ ConfigLog * location_get_config_log (Location *location) { g_return_val_if_fail (location != NULL, FALSE); g_return_val_if_fail (IS_LOCATION (location), FALSE); return location->p->config_log; } /** * location_store_full_snapshot: * @location: * * Gets XML snapshot data from all the backends contained in this location and * archives those data * * Return value: 0 on success and -1 if any of the backends failed **/ gint location_store_full_snapshot (Location *location) { int fd; gint ret; FILE *pipe; GList *c; BackendNote *note; g_return_val_if_fail (location != NULL, -1); g_return_val_if_fail (IS_LOCATION (location), -1); for (c = location->p->contains_list; c; c = c->next) { note = c->data; DEBUG_MSG ("Storing %s", note->backend_id); fd = run_backend_proc (note->backend_id, TRUE, NULL); pipe = fdopen (fd, "r"); ret = location_store (location, note->backend_id, pipe, ConfigArchiver_STORE_DEFAULT); fclose (pipe); if (ret < 0) return -1; } return 0; } /** * location_garbage_collect: * @location: * * Iterates through backends and eliminates excess archived data from the * configuration log and the XML archive **/ static void garbage_collect_cb (ConfigLog *config_log, gchar *backend_id, gint id, Location *location) { gchar *filename; filename = g_strdup_printf ("%s/%08x.xml", location->p->fullpath, id); DEBUG_MSG ("Removing %s", filename); unlink (filename); g_free (filename); } void location_garbage_collect (Location *location) { GList *node; BackendNote *note; g_return_if_fail (location != NULL); g_return_if_fail (IS_LOCATION (location)); for (node = location->p->contains_list; node != NULL; node = node->next) { note = node->data; config_log_garbage_collect (location->p->config_log, note->backend_id, (GarbageCollectCB) garbage_collect_cb, location); } } /** * location_is_deleted: * @location: * * Returns TRUE iff the location is marked deleted **/ gboolean location_is_deleted (const Location *location) { return location->p->deleted; } static gint get_backends_cb (BackendList *backend_list, gchar *backend_id, Location *location) { location->p->contains_list = g_list_prepend (location->p->contains_list, backend_note_new (backend_id, FALSE)); return 0; } /* Construct the directory structure for a given location * * FIXME: Better error reporting */ static gboolean do_create (Location *location) { gint ret = 0; g_return_val_if_fail (location != NULL, FALSE); g_return_val_if_fail (IS_LOCATION (location), FALSE); g_return_val_if_fail (location->p->archive != NULL, FALSE); g_return_val_if_fail (IS_ARCHIVE (location->p->archive), FALSE); g_return_val_if_fail (location->p->locid != NULL, FALSE); if (location->p->parent == NULL) backend_list_foreach (archive_get_backend_list (location->p->archive), (BackendCB) get_backends_cb, location); if (g_file_test (archive_get_prefix (location->p->archive), G_FILE_TEST_ISDIR) == FALSE) return FALSE; if (location->p->fullpath) g_free (location->p->fullpath); location->p->fullpath = g_concat_dir_and_file (archive_get_prefix (location->p->archive), location->p->locid); if (g_file_test (location->p->fullpath, G_FILE_TEST_ISDIR) == FALSE) ret = mkdir (location->p->fullpath, S_IREAD | S_IWRITE | S_IEXEC); if (ret == -1) return FALSE; location->p->config_log = CONFIG_LOG (config_log_open (location)); return TRUE; } /* Load data for a location into the structure. Assumes all the * structures are already initialized. * * Returns TRUE on success and FALSE on failure (directory not present * or config metadata not present) */ static gboolean do_load (Location *location) { gchar *metadata_filename; g_return_val_if_fail (location != NULL, FALSE); g_return_val_if_fail (IS_LOCATION (location), FALSE); g_return_val_if_fail (location->p->archive != NULL, FALSE); g_return_val_if_fail (IS_ARCHIVE (location->p->archive), FALSE); g_return_val_if_fail (location->p->locid != NULL, FALSE); if (location->p->fullpath) g_free (location->p->fullpath); location->p->fullpath = g_concat_dir_and_file (archive_get_prefix (location->p->archive), location->p->locid); if (g_file_test (location->p->fullpath, G_FILE_TEST_ISDIR) == FALSE) return FALSE; metadata_filename = g_concat_dir_and_file (location->p->fullpath, "location.xml"); if (!load_metadata_file (location, metadata_filename, FALSE)) return FALSE; g_free (metadata_filename); location->p->config_log = CONFIG_LOG (config_log_open (location)); if (location->p->config_log == NULL) return FALSE; return TRUE; } static gboolean load_metadata_file (Location *location, char *filename, gboolean is_default) { xmlDocPtr doc; xmlNodePtr root_node, node; char *inherits_str = NULL, *label_str = NULL, *contains_str, *type_str; GList *list_head = NULL, *list_tail = NULL; BackendNote *note; ContainmentType type; g_return_val_if_fail (location != NULL, FALSE); g_return_val_if_fail (IS_LOCATION (location), FALSE); g_return_val_if_fail (location->p->archive != NULL, FALSE); g_return_val_if_fail (IS_ARCHIVE (location->p->archive), FALSE); g_return_val_if_fail (filename != NULL, FALSE); doc = xmlParseFile (filename); if (!doc) return FALSE; root_node = xmlDocGetRootElement (doc); if (strcmp (root_node->name, "location")) { xmlFreeDoc (doc); return FALSE; } for (node = root_node->childs; node; node = node->next) { if (!strcmp (node->name, "inherits")) { inherits_str = xmlGetProp (node, "location"); if (inherits_str == NULL) g_warning ("Bad location config: " \ "inherits element with no " \ "location attribute"); } else if (!strcmp (node->name, "label")) { label_str = xmlNodeGetContent (node); } else if (!strcmp (node->name, "contains")) { contains_str = xmlGetProp (node, "backend"); type_str = xmlGetProp (node, "type"); if (contains_str != NULL) { if (!strcmp (type_str, "full")) type = ConfigArchiver_CONTAIN_FULL; else if (!strcmp (type_str, "partial")) type = ConfigArchiver_CONTAIN_PARTIAL; else { type = ConfigArchiver_CONTAIN_NONE; g_warning ("Bad type attribute"); } note = backend_note_new (contains_str, type); list_tail = g_list_append (list_tail, note); if (list_head == NULL) list_head = list_tail; else list_tail = list_tail->next; } else { g_warning ("Bad location config: " \ "contains element with no " \ "backend attribute"); } } } xmlFreeDoc (doc); location->p->contains_list = list_head; if (!is_default) { if (inherits_str != NULL) { location->p->parent = archive_get_location (location->p->archive, inherits_str); } else { if (list_head != NULL) { g_warning ("Top-level locations should not " \ "have contains lists"); g_list_foreach (list_head, (GFunc) g_free, NULL); g_list_free (list_head); } backend_list_foreach (archive_get_backend_list (location->p->archive), (BackendCB) get_backends_cb, location); } } if (label_str != NULL) { if (location->p->label != NULL) g_free (location->p->label); location->p->label = label_str; } return TRUE; } static void save_metadata (Location *location) { gchar *metadata_filename; if (location->p->deleted || (!location->p->is_new && !location->p->contains_list_dirty)) return; location->p->is_new = FALSE; metadata_filename = g_concat_dir_and_file (location->p->fullpath, "location.xml"); write_metadata_file (location, metadata_filename); g_free (metadata_filename); location->p->contains_list_dirty = FALSE; } static void write_metadata_file (Location *location, gchar *filename) { GList *node; xmlDocPtr doc; xmlNodePtr root_node, child_node; BackendNote *note; doc = xmlNewDoc ("1.0"); root_node = xmlNewDocNode (doc, NULL, "location", NULL); if (location->p->parent) { g_return_if_fail (location->p->parent->p->locid != NULL); child_node = xmlNewChild (root_node, NULL, "inherits", NULL); xmlNewProp (child_node, "location", location->p->parent->p->locid); for (node = location->p->contains_list; node; node = node->next) { note = node->data; child_node = xmlNewChild (root_node, NULL, "contains", NULL); xmlNewProp (child_node, "backend", note->backend_id); if (note->type == ConfigArchiver_CONTAIN_PARTIAL) xmlNewProp (child_node, "type", "partial"); else if (note->type == ConfigArchiver_CONTAIN_FULL) xmlNewProp (child_node, "type", "full"); } } xmlNewChild (root_node, NULL, "label", location->p->label); xmlDocSetRootElement (doc, root_node); /* FIXME: Report errors here */ xmlSaveFile (filename, doc); xmlFreeDoc (doc); } /* Run the given backend and return the file descriptor used to read or write * XML to it */ static gint run_backend_proc (gchar *backend_id, gboolean do_get, pid_t *pid_r) { char *args[3]; int fd[2]; pid_t pid; int p_fd, c_fd; if (pipe (fd) == -1) return -1; pid = fork (); p_fd = do_get ? 0 : 1; c_fd = do_get ? 1 : 0; if (pid == (pid_t) -1) { return -1; } else if (pid == 0) { int i; dup2 (fd[c_fd], c_fd); close (fd[p_fd]); for (i = 3; i < FOPEN_MAX; i++) close (i); if (strstr (backend_id, "-conf")) args[0] = g_concat_dir_and_file (XST_BACKEND_LOCATION, backend_id); else args[0] = gnome_is_program_in_path (backend_id); args[1] = do_get ? "--get" : "--set"; args[2] = NULL; if (args[0] == NULL) { g_warning ("Backend not in path: %s", backend_id); close (c_fd); exit (-1); } execv (args[0], args); g_warning ("Could not launch backend %s: %s", args[0], g_strerror (errno)); exit (-1); return 0; } else { if (pid_r != NULL) *pid_r = pid; close (fd[c_fd]); return fd[p_fd]; } } static BackendNote * backend_note_new (const gchar *backend_id, ContainmentType type) { BackendNote *note; note = g_new0 (BackendNote, 1); note->backend_id = g_strdup (backend_id); note->type = type; return note; } static void backend_note_destroy (BackendNote *note) { g_free (note->backend_id); g_free (note); } static const BackendNote * find_note (Location *location, const gchar *backend_id) { GList *node; for (node = location->p->contains_list; node; node = node->next) if (!strcmp (((BackendNote *)node->data)->backend_id, backend_id)) return node->data; return NULL; } /* Create a list of backends that differ between location1 and the common * parent of location1 and location2 */ static GList * create_backends_list (Location *location1, Location *location2) { Location *loc; GList *location_path, *tail = NULL, *c, *tmp; location_path = location_find_path_from_common_parent (location1, location2); /* Skip the first entry -- it is the common parent */ tmp = location_path; location_path = location_path->next; g_list_free_1 (tmp); while (location_path != NULL) { if (location_path->data != NULL) { loc = LOCATION (location_path->data); for (c = loc->p->contains_list; c; c = c->next) tail = g_list_prepend (tail, ((BackendNote *)c->data)->backend_id); } tmp = location_path; location_path = location_path->next; g_list_free_1 (tmp); } return g_list_reverse (tail); } /* Merge two backend lists, eliminating duplicates */ static GList * merge_backend_lists (GList *backends1, GList *backends2) { GList *head = NULL, *tail = NULL, *tmp; int res; backends1 = g_list_sort (backends1, (GCompareFunc) strcmp); backends2 = g_list_sort (backends2, (GCompareFunc) strcmp); while (backends1 && backends2) { res = strcmp (backends1->data, backends2->data); if (res < 0) { if (tail != NULL) tail->next = backends1; else head = backends1; tail = backends1; backends1 = backends1->next; } else if (res > 0) { if (tail != NULL) tail->next = backends2; else head = backends2; tail = backends2; backends2 = backends2->next; } else { if (tail != NULL) tail->next = backends1; else head = backends1; tail = backends1; backends1 = backends1->next; tmp = backends2; backends2 = backends2->next; g_list_free_1 (tmp); } } if (backends1 != NULL) { if (tail != NULL) tail->next = backends1; else head = backends1; } else { if (tail != NULL) tail->next = backends2; else head = backends2; } return head; }