Added archiver directory Imported archiver for time travel, location

Tue Dec 19 09:04:00 2000  Bradford Hovinen  <hovinen@helixcode.com>

	* Makefile.am (SUBDIRS): Added archiver directory
	* archiver/*: Imported archiver for time travel, location management
This commit is contained in:
Bradford Hovinen 2000-12-19 14:05:04 +00:00 committed by Bradford Hovinen (Gdict maintainer)
parent d2df694453
commit f089da2886
17 changed files with 4367 additions and 0 deletions

149
archiver/ChangeLog Normal file
View file

@ -0,0 +1,149 @@
2000-12-18 Bradford Hovinen <hovinen@helixcode.com>
* main.c (do_rollback): Support rolling back by steps
* location.c (location_rollback_backend_by): Implement
(location_dump_rollback_data): Support passing steps as well as
date
* config-log.c (config_log_get_rollback_id_by_steps): Implement
* location.c (write_metadata_file): Don't support writing out the
master list any more
(save_metadata): Ditto
(load_metadata_file): Get backends list from BackendList object
(rather than finding it out oneself) if location is toplevel
(do_create): Ditto
(get_backends_cb): Implement
(location_add_backend): Add return values for error conditions
2000-10-15 Bradford Hovinen <hovinen@helixcode.com>
* location.c (location_rollback_backends_to): Free id_array when done
* config-log.c (config_log_reset_filenames): Implement
* archive.c (archive_set_current_location_id): Move gnome_config_
code from set_current_location
* location.c (location_set_id): Implement
* main.c (do_rename_location): Implement
* archive.c (free_location_cb): Don't free locid
(archive_get_location):
(archive_register_location): Don't strdup the location id
(archive_set_current_location_id): Implement
* main.c: Add options for renaming locations and specifying that
backends should be added to or removed from the master list
* location.c (run_backend_proc): Close all descriptors other than
0, 1, 2
(location_set_id): Implement
(location_set_arg): Free previous locid if exists
* archive.c (add_location_cb): Implement
(archive_set_current_location): Add location change algorithm
* location.c (location_find_path_from_common_parent): Implement
(location_foreach_backend): Implement
* archive.c (archive_set_current_location): Set
archive->current_location_id
* main.c (do_add_location): Check parent_str for NULL before
loading location
(do_change_location): Implement
(main): Support changing location
* archive.c (archive_register_location): Implement
* location.c (do_create):
(do_load): Use archive_get_prefix
(do_create): Load global location metadata if this location does
not inherit from anything
(location_get_id): Implement
* archive.c (archive_get_prefix): Implement
* location.c (location_get_path): Implement
(load_metadata_file): Use archive_is_global
(location_set_arg): Set label when setting locid
* location.[ch]: Make all data members private
* main.c (main): Get current location name properly, create
location iff location id is "default"; bail out with error otherwise
(do_remove_location):
(do_add_location): Implement
* config-log.c: Include ctype.h to fix implicit declarations
* location.c (location_rollback_backends_to): Property initialize i
(location_contains): Fix precondition checks
(do_create): Remove unused variables
* archive.c (archive_get_location): Make a copy of locid before
working with it
(do_load): Fix precondition checks
* location.c (location_open):
(location_new): Make locid const
* archive.c (archive_get_location): Make locid const
* location.c (data_delete_cb):
(location_delete): Implement
* config-log.c (config_log_iterate): Implement
(config_log_destroy): Make private, pass GtkObject
(config_log_delete): Implement
2000-10-14 Bradford Hovinen <hovinen@helixcode.com>
* location.c (location_close): Add precondition checks
* archive.c (archive_destroy): Make private; pass GtkObject
* location.c (location_add_backend):
(location_remove_backend): Add precondition checks
(location_destroy): Make private
(location_destroy): Pass GtkObject pointer rather than Location
pointer; this cleans up assignment in _class_init
* archive.c (archive_get_current_location_id): Implement
(archive_get_current_location): Implement
2000-09-03 Bradford Hovinen <hovinen@helixcode.com>
* location.c (location_new): Set is_new
(save_metadata): Don't write file unless necessary
(save_metadata):
(write_metadata_file): Split out file writing into
write_metadata_file; write default data file as necessary
(location_add_backend):
(location_remove_backend): Implement
* main.c: Add command line arguments for adding/removing locations
and backends; move backend id argument into global options
* location.c (do_load):
(load_metadata_file): Split out file parsing logic into
load_metadata_file; call load_metadata_file on default file to get
contains list if current location doesn't inherit anything
(load_metadata_file): g_strdup backend string
(load_metadata_file): Warn if top-level location has contains clauses
(location_store): Check if the location contains the given backend
and try the location it inherits if it doesn't
* archive.c (archive_load): Set is_global in archive object
* Makefile.am (Locationmeta{dir|_DATA}): Added commands to install
data files
(INCLUDES): Added define for LOCATION_DIR
2000-09-03 Bradford Hovinen <hovinen@helixcode.com>
* location.c (save_metadata): Write attributes when saving

26
archiver/Makefile.am Normal file
View file

@ -0,0 +1,26 @@
Locationmetadir = $(datadir)/hcm/default
Locationmeta_DATA = default-user.xml default-global.xml
INCLUDES = \
-DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
-I$(includedir) $(GNOME_INCLUDEDIR) \
-DVERSION=\""$(VERSION)"\" \
-DCONFIGDIR=\""/etc"\" \
-DLOCATION_DIR=\""$(datadir)/hcm/default"\" \
$(XML_CFLAGS)
bin_PROGRAMS = archiver
archiver_SOURCES = \
main.c \
archive.c archive.h \
location.c location.h \
config-log.c config-log.h \
backend-list.c backend-list.h \
util.c util.h
archiver_LDADD = \
$(GNOME_LIBDIR) \
$(GNOMEUI_LIBS) \
$(INTLLIBS) \
$(GNOME_XML_LIB)

34
archiver/TODO Normal file
View file

@ -0,0 +1,34 @@
* Add per-user master list
* Archiving changes in location metadata
* Fix race in lock handling and add timeout support (look in gnome-mime)
* Support multiple backends from CLI
Long-term
* Add clustering support:
- Add Cluster class inheriting Archive class and overriding path
semantics
- Change location rollback functionality to send data through to
clients if the archive is a cluster
* Allow backend specs to identify an order in which they should be applied
- Specify this in the master list; have each location look up that
information before invoking multiple backends
Questions
Done
* Global list of configs for a given archive
* Location should store backend data in the location where it is valid
* Add support for dumping XML to stdout rather than running the backend
* Fix bug where EOF not sent through pipe
* Changing the name of a location
* Adding per-user/global backends
- Don't try to write out contains list on toplevel locations
- Give error if the user tries to add a backend to a toplevel location
* Consistency check on adding and removing backends
- Make sure the backend is included in the global metadata list before
adding
- When removing global and per-user backends, mark the backend
"invalid" and exclude from location_foreach_backend,
location_rollback_all_to, and location_contains.
* Refactor master list into an attribute of Archive
* Roll back x number of steps rather than by date

523
archiver/archive.c Normal file
View file

@ -0,0 +1,523 @@
/* -*- mode: c; style: linux -*- */
/* archive.c
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen (hovinen@helixcode.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 <config.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include "archive.h"
static GtkObjectClass *parent_class;
enum {
ARG_0,
ARG_PREFIX
};
static void archive_init (Archive *archive);
static void archive_class_init (ArchiveClass *klass);
static void archive_destroy (GtkObject *object);
static void archive_set_arg (GtkObject *object,
GtkArg *arg,
guint arg_id);
static void archive_get_arg (GtkObject *object,
GtkArg *arg,
guint arg_id);
static gboolean do_load (Archive *archive);
guint
archive_get_type (void)
{
static guint archive_type;
if (!archive_type) {
GtkTypeInfo archive_info = {
"Archive",
sizeof (Archive),
sizeof (ArchiveClass),
(GtkClassInitFunc) archive_class_init,
(GtkObjectInitFunc) archive_init,
(GtkArgSetFunc) NULL,
(GtkArgGetFunc) NULL
};
archive_type =
gtk_type_unique (gtk_object_get_type (),
&archive_info);
}
return archive_type;
}
static void
archive_init (Archive *archive)
{
archive->prefix = NULL;
archive->locations = g_tree_new ((GCompareFunc) strcmp);
archive->current_location_id = NULL;
}
static void
archive_class_init (ArchiveClass *klass)
{
GtkObjectClass *object_class;
object_class = GTK_OBJECT_CLASS (klass);
object_class->destroy = archive_destroy;
object_class->set_arg = archive_set_arg;
object_class->get_arg = archive_get_arg;
gtk_object_add_arg_type ("Archive::prefix",
GTK_TYPE_POINTER,
GTK_ARG_READWRITE,
ARG_PREFIX);
parent_class = gtk_type_class (gtk_object_get_type ());
}
static void
archive_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
Archive *archive;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_ARCHIVE (object));
g_return_if_fail (arg != NULL);
archive = ARCHIVE (object);
switch (arg_id) {
case ARG_PREFIX:
if (GTK_VALUE_POINTER (*arg) != NULL)
archive->prefix = g_strdup (GTK_VALUE_POINTER (*arg));
break;
default:
break;
}
}
static void
archive_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
Archive *archive;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_ARCHIVE (object));
g_return_if_fail (arg != NULL);
archive = ARCHIVE (object);
switch (arg_id) {
case ARG_PREFIX:
GTK_VALUE_POINTER (*arg) = archive->prefix;
break;
default:
arg->type = GTK_TYPE_INVALID;
break;
}
}
/**
* archive_load:
* @is_global: TRUE iff we should load the global archive
*
* Load either the global or per-user configuration archive
*
* Return value: Reference to archive
**/
GtkObject *
archive_load (gboolean is_global)
{
GtkObject *object;
gchar *prefix;
if (is_global)
prefix = CONFIGDIR "/helix-config";
else
prefix = g_concat_dir_and_file (g_get_home_dir (),
".gnome/helix-config");
object = gtk_object_new (archive_get_type (),
"prefix", prefix,
NULL);
if (is_global)
g_free (prefix);
if (do_load (ARCHIVE (object)) == FALSE) {
gtk_object_destroy (object);
return NULL;
}
ARCHIVE (object)->is_global = is_global;
ARCHIVE (object)->backend_list =
BACKEND_LIST (backend_list_new (is_global));
return object;
}
static gint
free_location_cb (gchar *locid, Location *location)
{
location_close (location);
return FALSE;
}
static void
archive_destroy (GtkObject *object)
{
Archive *archive;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_ARCHIVE (object));
archive = ARCHIVE (object);
g_tree_traverse (archive->locations,
(GTraverseFunc) free_location_cb,
G_IN_ORDER,
NULL);
g_tree_destroy (archive->locations);
if (archive->current_location_id != NULL)
g_free (archive->current_location_id);
GTK_OBJECT_CLASS (parent_class)->destroy (GTK_OBJECT (archive));
}
/**
* archive_close:
* @archive:
*
* Closes the given archive handle. Also closes all locations under this
* archive.
**/
void
archive_close (Archive *archive)
{
g_return_if_fail (archive != NULL);
g_return_if_fail (IS_ARCHIVE (archive));
gtk_object_destroy (GTK_OBJECT (archive));
}
/**
* archive_get_location:
* @archive:
* @locid:
*
* Get a reference to the location with the given name.
*
* Return value: Reference to location, NULL if no such location exists
**/
Location *
archive_get_location (Archive *archive, const gchar *locid)
{
GtkObject *loc_obj;
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);
loc_obj = g_tree_lookup (archive->locations, locid);
if (!loc_obj) {
loc_obj = location_open (archive, locid);
if (!loc_obj) return NULL;
if (loc_obj)
g_tree_insert (archive->locations, locid, loc_obj);
}
if (loc_obj) {
gtk_object_ref (loc_obj);
return LOCATION (loc_obj);
} else {
return NULL;
}
}
/**
* archive_register_location:
* @archive:
* @location:
*
* Register a location with the archive; invoked by location_new
**/
void
archive_register_location (Archive *archive, Location *location)
{
g_return_if_fail (archive != NULL);
g_return_if_fail (IS_ARCHIVE (archive));
g_return_if_fail (location != NULL);
g_return_if_fail (IS_LOCATION (location));
g_tree_insert (archive->locations,
location_get_id (location),
location);
}
/**
* archive_unregister_location:
* @archive:
* @location:
*
* Unregisters a location from the archive
**/
void
archive_unregister_location (Archive *archive, Location *location)
{
g_return_if_fail (archive != NULL);
g_return_if_fail (IS_ARCHIVE (archive));
g_return_if_fail (location != NULL);
g_return_if_fail (IS_LOCATION (location));
/* FIXME: We might be screwing things up here if we're traversing... */
g_tree_remove (archive->locations, location);
}
/**
* archive_get_current_location:
* @archive: object
*
* Convenience function to get a pointer to the current location
*
* Return value: Pointer to current location
**/
Location *
archive_get_current_location (Archive *archive)
{
g_return_val_if_fail (archive != NULL, NULL);
g_return_val_if_fail (IS_ARCHIVE (archive), NULL);
return archive_get_location (archive,
archive_get_current_location_id
(archive));
}
static int
add_location_cb (Location *location, gchar *backend_id, GList *backends)
{
g_list_insert (backends, backend_id, 1);
return 0;
}
/**
* archive_set_current_location:
* @archive: object
* @location: Location to which to set archive
*
* Set the current location in an archive to the location given; apply
* configuration for the new location to all backends necessary
**/
void
archive_set_current_location (Archive *archive, Location *location)
{
GList *location_path, *backends;
Location *old_location = archive_get_current_location (archive);
g_return_if_fail (archive != NULL);
g_return_if_fail (IS_ARCHIVE (archive));
g_return_if_fail (location != NULL);
g_return_if_fail (IS_LOCATION (location));
archive_set_current_location_id (archive, location_get_id (location));
location_path = location_find_path_from_common_parent
(location, old_location);
backends = g_list_append (NULL, NULL);
while (location_path != NULL) {
if (location_path->data != NULL) {
location_foreach_backend
(LOCATION (location_path->data),
(LocationBackendCB) add_location_cb,
backends);
}
location_path = location_path->next;
}
location_rollback_backends_to (location, NULL, backends->next, TRUE);
g_list_free (backends);
}
/**
* archive_set_current_location_id:
* @archive:
* @name:
*
* Sets the current location's name, but does not invoke any rollback
**/
void
archive_set_current_location_id (Archive *archive, const gchar *locid)
{
g_return_if_fail (archive != NULL);
g_return_if_fail (IS_ARCHIVE (archive));
g_return_if_fail (locid != NULL);
if (archive->current_location_id != NULL)
g_free (archive->current_location_id);
archive->current_location_id = g_strdup (locid);
if (archive->is_global)
gnome_config_push_prefix ("=" LOCATION_DIR "=");
else
gnome_config_push_prefix ("helix-config/");
gnome_config_set_string ("config/current/location",
archive->current_location_id);
gnome_config_pop_prefix ();
gnome_config_sync ();
}
/**
* archive_get_current_location_id:
* @archive: object
*
* Get the name of the current location
*
* Return value: String containing current location, should not be freed
**/
const gchar *
archive_get_current_location_id (Archive *archive)
{
g_return_val_if_fail (archive != NULL, NULL);
g_return_val_if_fail (IS_ARCHIVE (archive), NULL);
if (archive->current_location_id == NULL) {
if (archive->is_global)
gnome_config_push_prefix ("=" LOCATION_DIR "=");
else
gnome_config_push_prefix ("helix-config/");
archive->current_location_id =
gnome_config_get_string
("config/current/location=default");
gnome_config_pop_prefix ();
}
return archive->current_location_id;
}
/**
* archive_get_prefix:
* @archive:
*
* Get the prefix for locations in this archive
*
* Return value: String containing prefix; should not be freed
**/
const gchar *
archive_get_prefix (Archive *archive)
{
g_return_val_if_fail (archive != NULL, FALSE);
g_return_val_if_fail (IS_ARCHIVE (archive), FALSE);
return archive->prefix;
}
/**
* archive_is_global:
* @archive:
*
* Tell whether the archive is global or per-user
*
* Return value: TRUE if global, FALSE if per-user
**/
gboolean
archive_is_global (Archive *archive)
{
g_return_val_if_fail (archive != NULL, FALSE);
g_return_val_if_fail (IS_ARCHIVE (archive), FALSE);
return archive->is_global;
}
/**
* archive_get_backend_list:
* @archive:
*
* Get the master backend list for this archive
*
* Return value: Reference to the master backend list
**/
BackendList *
archive_get_backend_list (Archive *archive)
{
g_return_val_if_fail (archive != NULL, FALSE);
g_return_val_if_fail (IS_ARCHIVE (archive), FALSE);
return archive->backend_list;
}
/* Load the archive information from disk; return TRUE on success and FALSE on
* failure
*/
static gboolean
do_load (Archive *archive)
{
gint ret = 0;
g_return_val_if_fail (archive != NULL, FALSE);
g_return_val_if_fail (IS_ARCHIVE (archive), FALSE);
g_return_val_if_fail (archive->prefix != NULL, FALSE);
if (g_file_test (archive->prefix, G_FILE_TEST_ISDIR) == FALSE)
ret = mkdir (archive->prefix, S_IREAD | S_IWRITE | S_IEXEC);
if (ret == -1) return FALSE;
return TRUE;
}

77
archiver/archive.h Normal file
View file

@ -0,0 +1,77 @@
/* -*- mode: c; style: linux -*- */
/* archive.h
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen (hovinen@helixcode.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.
*/
#ifndef __ARCHIVE_H
#define __ARCHIVE_H
#include <gnome.h>
#include "location.h"
#include "backend-list.h"
#define ARCHIVE(obj) GTK_CHECK_CAST (obj, archive_get_type (), Archive)
#define ARCHIVE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, archive_get_type (), ArchiveClass)
#define IS_ARCHIVE(obj) GTK_CHECK_TYPE (obj, archive_get_type ())
typedef struct _ArchiveClass ArchiveClass;
struct _Archive
{
GtkObject object;
gchar *prefix;
GTree *locations;
gboolean is_global;
gchar *current_location_id;
BackendList *backend_list;
};
struct _ArchiveClass
{
GtkObjectClass parent;
};
guint archive_get_type (void);
GtkObject *archive_load (gboolean is_global);
void archive_close (Archive *archive);
Location *archive_get_location (Archive *archive, const gchar *location);
void archive_register_location (Archive *archive, Location *location);
void archive_unregister_location (Archive *archive, Location *location);
Location *archive_get_current_location (Archive *archive);
void archive_set_current_location (Archive *archive, Location *location);
const gchar *archive_get_current_location_id (Archive *archive);
void archive_set_current_location_id (Archive *archive, const gchar *locid);
const gchar *archive_get_prefix (Archive *archive);
gboolean archive_is_global (Archive *archive);
BackendList *archive_get_backend_list (Archive *archive);
#endif /* __ARCHIVE */

77
archiver/archiver-spec Normal file
View file

@ -0,0 +1,77 @@
Rollback archiving internals
Copyright (C) 2000 Helix Code, Inc.
Written by Bradford Hovinen <hovinen@helixcode.com>
1. Directory format
Diagram:
+ toplevel
|-+ Location 1
| |- CCYYMMDD-hhmm-<id>.xml
| | .
| | .
| | .
| |- metadata.log:
| | [<id> <date> <time> <backend> ] ^
| | [ . ] | Time
| | [ . ] |
| | [ . ] |
| \- metadata.xml:
| [... ]
| [<inherits>location</inherits> ]
| [<valid-config>backend</valid-config> ]
| [... ]
|-+ Location 2
| ...
There is one toplevel directory for each archive. This directory
contains one or more location directories. Each location directory
must contain two files: an XML file describing the location and a log
of changes made in that location. Each change corresponds to an XML
file containing a snapshot of the configuration as modified. There is
one XML file per backend. Each change has an id number that is
incremented atomicall by the archiving script when it stores
configuration changes. The id number, as well as the date and time of
storage, form a filename that uniquely identifies each configuration
change. The archiving script must also store in the log file a line
with the id number, date and time of storage, and backend used
whenever it stores XML data. New entries are stored at the head of the
file, so that during rollback, the file may be foreward scanned to
find the appropriate identifier for the configuration file. The
per-location XML configuration file contains information on what the
location's parent is and what configurations that location defines.
For now, the backend shall be referred to by its executable name. When
the backends gain CORBA interfaces, I suggest that the OAF id be used
instead. This reduces the problem of setting a backend's configuration
to a simple object activation and method invocation. The OAF id may
also be used to resolve the backend's human-readable name.
2. Meta-configuration details
In order that this system be complete, there must be a way to
ascertain the current location and to roll back changes in location. I
propose that there be a special archive in the configuration hierarchy
that contains location history in the same format as other
locations. The archiver can then be a single script that accepts
command-line arguments describing the request action: `archive this
data', `roll back this backend's configuration', and `switch to this
location'. It then handles all the details of interfacing with the
archive and applying the changes in the correct order. Conceptually,
the archiver becomes a backend in and of itself, where the frontend is
located in the GUI of HCM. It would therefore be adviseable to use the
same standards for the archiver as for other backends and hence make
it a CORBA service, where the tool-specific interface is as described
above.
3. Future directions
The metafile log structure may run into scalability problems for
installations have have been in place for a long time. An alternative
structure that uses binary indexing might be in order. A command line
utility (with GUI interface) could be written to recover the file in
the case of corruption; such a utility could simply introspect each of
the XML files in a directory. Provided that each XML file contains
enough information to create a file entry, which is trivial, recovery
is assured.

305
archiver/backend-list.c Normal file
View file

@ -0,0 +1,305 @@
/* -*- mode: c; style: linux -*- */
/* backend-list.c
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen <hovinen@helixcode.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 "config.h"
#endif
#include <parser.h>
#include <tree.h>
#include "backend-list.h"
enum {
ARG_0,
ARG_IS_GLOBAL
};
struct _BackendListPrivate
{
gboolean is_global;
gchar *filename;
GList *backend_ids;
};
static GtkObjectClass *parent_class;
static void backend_list_init (BackendList *backend_list);
static void backend_list_class_init (BackendListClass *class);
static void backend_list_set_arg (GtkObject *object,
GtkArg *arg,
guint arg_id);
static void backend_list_get_arg (GtkObject *object,
GtkArg *arg,
guint arg_id);
static void backend_list_finalize (GtkObject *object);
static void do_load (BackendList *backend_list);
static void do_save (BackendList *backend_list);
guint
backend_list_get_type (void)
{
static guint backend_list_type = 0;
if (!backend_list_type) {
GtkTypeInfo backend_list_info = {
"BackendList",
sizeof (BackendList),
sizeof (BackendListClass),
(GtkClassInitFunc) backend_list_class_init,
(GtkObjectInitFunc) backend_list_init,
(GtkArgSetFunc) NULL,
(GtkArgGetFunc) NULL
};
backend_list_type =
gtk_type_unique (gtk_object_get_type (),
&backend_list_info);
}
return backend_list_type;
}
static void
backend_list_init (BackendList *backend_list)
{
backend_list->p = g_new0 (BackendListPrivate, 1);
}
static void
backend_list_class_init (BackendListClass *class)
{
GtkObjectClass *object_class;
gtk_object_add_arg_type ("BackendList::is-global",
GTK_TYPE_INT,
GTK_ARG_CONSTRUCT_ONLY | GTK_ARG_READWRITE,
ARG_IS_GLOBAL);
object_class = GTK_OBJECT_CLASS (class);
object_class->finalize = backend_list_finalize;
object_class->set_arg = backend_list_set_arg;
object_class->get_arg = backend_list_get_arg;
parent_class = GTK_OBJECT_CLASS
(gtk_type_class (gtk_object_get_type ()));
}
static void
backend_list_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
BackendList *backend_list;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_BACKEND_LIST (object));
backend_list = BACKEND_LIST (object);
switch (arg_id) {
case ARG_IS_GLOBAL:
backend_list->p->is_global = GTK_VALUE_INT (*arg);
backend_list->p->filename = backend_list->p->is_global ?
LOCATION_DIR "/default-global.xml" :
LOCATION_DIR "/default-user.xml";
do_load (backend_list);
break;
default:
g_warning ("Bad argument set");
break;
}
}
static void
backend_list_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
BackendList *backend_list;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_BACKEND_LIST (object));
backend_list = BACKEND_LIST (object);
switch (arg_id) {
case ARG_IS_GLOBAL:
GTK_VALUE_INT (*arg) = backend_list->p->is_global;
break;
default:
g_warning ("Bad argument get");
break;
}
}
static void
backend_list_finalize (GtkObject *object)
{
BackendList *backend_list;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_BACKEND_LIST (object));
backend_list = BACKEND_LIST (object);
g_list_foreach (backend_list->p->backend_ids, (GFunc) g_free, NULL);
g_list_free (backend_list->p->backend_ids);
g_free (backend_list->p);
GTK_OBJECT_CLASS (parent_class)->finalize (object);
}
GtkObject *
backend_list_new (gboolean is_global)
{
return gtk_object_new (backend_list_get_type (),
"is-global", is_global,
NULL);
}
gboolean
backend_list_contains (BackendList *backend_list, gchar *backend_id)
{
g_return_val_if_fail (backend_list != NULL, FALSE);
g_return_val_if_fail (IS_BACKEND_LIST (backend_list), FALSE);
return (g_list_find (backend_list->p->backend_ids, backend_id) !=
NULL);
}
/**
* backend_list_foreach:
* @backend_list:
* @callback:
* @data:
*
* Iterates through all the backends, invoking the callback given and aborting
* if any callback returns a nonzero value
*
* Return value: TRUE iff no callback issued a nonzero value, FALSE otherwise
**/
gboolean
backend_list_foreach (BackendList *backend_list, BackendCB callback,
gpointer data)
{
GList *node;
g_return_val_if_fail (backend_list != NULL, FALSE);
g_return_val_if_fail (IS_BACKEND_LIST (backend_list), FALSE);
g_return_val_if_fail (callback != NULL, FALSE);
for (node = backend_list->p->backend_ids; node; node = node->next)
if (callback (backend_list, node->data, data)) return FALSE;
return TRUE;
}
void
backend_list_add (BackendList *backend_list, gchar *backend_id)
{
g_return_if_fail (backend_list != NULL);
g_return_if_fail (IS_BACKEND_LIST (backend_list));
g_return_if_fail (backend_id != NULL);
backend_list->p->backend_ids =
g_list_prepend (backend_list->p->backend_ids, backend_id);
}
void
backend_list_remove (BackendList *backend_list, gchar *backend_id)
{
g_return_if_fail (backend_list != NULL);
g_return_if_fail (IS_BACKEND_LIST (backend_list));
g_return_if_fail (backend_id != NULL);
backend_list->p->backend_ids =
g_list_remove (backend_list->p->backend_ids, backend_id);
}
void
backend_list_save (BackendList *backend_list)
{
g_return_if_fail (backend_list != NULL);
g_return_if_fail (IS_BACKEND_LIST (backend_list));
do_save (backend_list);
}
static void
do_load (BackendList *backend_list)
{
xmlNodePtr root_node, node;
xmlDocPtr doc;
GList *list_tail = NULL;
gchar *contains_str;
doc = xmlParseFile (backend_list->p->filename);
if (doc == NULL) return;
root_node = xmlDocGetRootElement (doc);
for (node = root_node->childs; node; node = node->next) {
if (!strcmp (node->name, "contains")) {
contains_str = xmlGetProp (node, "backend");
if (contains_str != NULL) {
contains_str = g_strdup (contains_str);
list_tail = g_list_append (list_tail,
contains_str);
if (backend_list->p->backend_ids == NULL)
backend_list->p->backend_ids =
list_tail;
else
list_tail = list_tail->next;
} else {
g_warning ("Bad backends list: " \
"contains element with no " \
"backend attribute");
}
}
}
}
static void
do_save (BackendList *backend_list)
{
xmlNodePtr root_node, child_node;
xmlDocPtr doc;
GList *node;
doc = xmlNewDoc ("1.0");
root_node = xmlNewDocNode (doc, NULL, "location", NULL);
for (node = backend_list->p->backend_ids; node; node = node->next) {
child_node = xmlNewChild (root_node, NULL, "contains", NULL);
xmlNewProp (child_node, "backend", node->data);
}
xmlDocSetRootElement (doc, root_node);
xmlSaveFile (backend_list->p->filename, doc);
xmlFreeDoc (doc);
}

73
archiver/backend-list.h Normal file
View file

@ -0,0 +1,73 @@
/* -*- mode: c; style: linux -*- */
/* backend-list.h
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen <hovinen@helixcode.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.
*/
#ifndef __BACKEND_LIST_H
#define __BACKEND_LIST_H
#include <gnome.h>
BEGIN_GNOME_DECLS
#define BACKEND_LIST(obj) GTK_CHECK_CAST (obj, backend_list_get_type (), BackendList)
#define BACKEND_LIST_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, backend_list_get_type (), BackendListClass)
#define IS_BACKEND_LIST(obj) GTK_CHECK_TYPE (obj, backend_list_get_type ())
typedef struct _BackendList BackendList;
typedef struct _BackendListClass BackendListClass;
typedef struct _BackendListPrivate BackendListPrivate;
typedef gint (*BackendCB) (BackendList *, gchar *, gpointer);
struct _BackendList
{
GtkObject parent;
BackendListPrivate *p;
};
struct _BackendListClass
{
GtkObjectClass gtk_object_class;
};
guint backend_list_get_type (void);
GtkObject *backend_list_new (gboolean is_global);
gboolean backend_list_contains (BackendList *backend_list,
gchar *backend_id);
gboolean backend_list_foreach (BackendList *backend_list,
BackendCB callback,
gpointer data);
void backend_list_add (BackendList *backend_list,
gchar *backend_id);
void backend_list_remove (BackendList *backend_list,
gchar *backend_id);
void backend_list_save (BackendList *backend_list);
END_GNOME_DECLS
#endif /* __BACKEND_LIST_H */

933
archiver/config-log.c Normal file
View file

@ -0,0 +1,933 @@
/* -*- mode: c; style: linux -*- */
/* config-log.c
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen (hovinen@helixcode.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 <config.h>
#endif
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include "config-log.h"
#include "location.h"
#include "util.h"
static GtkObjectClass *parent_class;
enum {
ARG_0,
ARG_LOCATION
};
typedef struct _ConfigLogEntry ConfigLogEntry;
struct _ConfigLogEntry
{
gint id;
struct tm *date;
gchar *backend_id;
};
static void config_log_init (ConfigLog *config_log);
static void config_log_class_init (ConfigLogClass *klass);
static void config_log_set_arg (GtkObject *object,
GtkArg *arg,
guint arg_id);
static void config_log_get_arg (GtkObject *object,
GtkArg *arg,
guint arg_id);
static void config_log_destroy (GtkObject *object);
static GList *find_config_log_entry_id (ConfigLog *config_log,
GList *start,
gint id);
static GList *find_config_log_entry_date (ConfigLog *config_log,
GList *start,
struct tm *date);
static GList *find_config_log_entry_backend (ConfigLog *config_log,
GList *start,
gchar *backend_id);
static GList *load_next_log_entry (ConfigLog *config_log,
GList *last);
static gchar *get_line (FILE *file);
static gboolean parse_line (char *buffer,
int *id,
struct tm *time,
char **backend_id);
static gboolean time_geq (struct tm *time1,
struct tm *time2);
static gboolean do_load (ConfigLog *config_log);
static void do_unload (ConfigLog *config_log);
static gint get_next_id (ConfigLog *config_log);
static struct tm *get_current_date (void);
static void write_log (FILE *output,
ConfigLogEntry *entry);
static void dump_log (ConfigLog *config_log);
static void config_log_entry_destroy (ConfigLogEntry *entry);
guint
config_log_get_type (void)
{
static guint config_log_type;
if (!config_log_type) {
GtkTypeInfo config_log_info = {
"ConfigLog",
sizeof (ConfigLog),
sizeof (ConfigLogClass),
(GtkClassInitFunc) config_log_class_init,
(GtkObjectInitFunc) config_log_init,
(GtkArgSetFunc) NULL,
(GtkArgGetFunc) NULL
};
config_log_type =
gtk_type_unique (gtk_object_get_type (),
&config_log_info);
}
return config_log_type;
}
static void
config_log_init (ConfigLog *config_log)
{
config_log->location = NULL;
}
static void
config_log_class_init (ConfigLogClass *klass)
{
GtkObjectClass *object_class;
object_class = GTK_OBJECT_CLASS (klass);
object_class->destroy = config_log_destroy;
object_class->set_arg = config_log_set_arg;
object_class->get_arg = config_log_get_arg;
gtk_object_add_arg_type ("ConfigLog::location",
GTK_TYPE_POINTER,
GTK_ARG_READWRITE,
ARG_LOCATION);
parent_class = gtk_type_class (gtk_object_get_type ());
}
static void
config_log_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
ConfigLog *config_log;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_CONFIG_LOG (object));
g_return_if_fail (arg != NULL);
config_log = CONFIG_LOG (object);
switch (arg_id) {
case ARG_LOCATION:
g_return_if_fail (GTK_VALUE_POINTER (*arg) != NULL);
g_return_if_fail (IS_LOCATION (GTK_VALUE_POINTER (*arg)));
config_log->location = GTK_VALUE_POINTER (*arg);
break;
default:
break;
}
}
static void
config_log_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
ConfigLog *config_log;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_CONFIG_LOG (object));
g_return_if_fail (arg != NULL);
config_log = CONFIG_LOG (object);
switch (arg_id) {
case ARG_LOCATION:
GTK_VALUE_POINTER (*arg) = config_log->location;
break;
default:
arg->type = GTK_TYPE_INVALID;
break;
}
}
/* Loads a configuration log. Creates it if it has not been created
* already
*/
GtkObject *
config_log_open (Location *location)
{
GtkObject *object;
object = gtk_object_new (config_log_get_type (),
"location", location,
NULL);
do_load (CONFIG_LOG (object));
return object;
}
/* Destroys a configuration log data structure and frees all memory
* associated with it, dumping the existing log out to disk
*/
static void
config_log_destroy (GtkObject *object)
{
ConfigLog *config_log;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_CONFIG_LOG (object));
config_log = CONFIG_LOG (object);
do_unload (config_log);
GTK_OBJECT_CLASS (parent_class)->destroy (GTK_OBJECT (config_log));
}
/**
* config_log_delete:
* @config_log:
*
* Permanently destroy a config log, including its log file. Also destory the
* object
**/
void
config_log_delete (ConfigLog *config_log)
{
g_return_if_fail (config_log != NULL);
g_return_if_fail (IS_CONFIG_LOG (config_log));
if (config_log->file != NULL) {
fclose (config_log->file);
config_log->file = NULL;
}
if (config_log->filename != NULL)
unlink (config_log->filename);
gtk_object_destroy (GTK_OBJECT (config_log));
}
/* Return the id number of the most recent data written by the given
* backend prior to the given date. If date is NULL, it is assumed to
* be today.
*/
gint
config_log_get_rollback_id_for_date (ConfigLog *config_log,
struct tm *date,
gchar *backend_id)
{
GList *node;
g_return_val_if_fail (config_log != NULL, -1);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), -1);
g_return_val_if_fail (backend_id != NULL, -1);
if (config_log->log_data == NULL)
config_log->log_data =
load_next_log_entry (config_log, NULL);
if (date == NULL)
node = config_log->log_data;
else
node = find_config_log_entry_date (config_log,
config_log->log_data,
date);
node = find_config_log_entry_backend (config_log, node, backend_id);
if (!node)
return -1;
else
return ((ConfigLogEntry *) node->data)->id;
}
/* Given a linked list of backend ids and a date, return an array of
* ids corresponding to the most recent data written by each of the
* backends prior to the given date. The array is in the same order as
* the backends and should be freed when done. If date is NULL, it is
* assumed to be today.
*
* FIXME: This should really sort the ids by date.
*/
gint *
config_log_get_rollback_ids_for_date (ConfigLog *config_log,
struct tm *date,
GList *backend_ids)
{
GList *start_node, *node;
gint *id_array, i = 0;
g_return_val_if_fail (config_log != NULL, NULL);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
g_return_val_if_fail (backend_ids != NULL, NULL);
if (config_log->log_data == NULL)
config_log->log_data =
load_next_log_entry (config_log, NULL);
if (date == NULL)
start_node = config_log->log_data;
else
start_node = find_config_log_entry_date (config_log,
config_log->log_data,
date);
id_array = g_new (gint, g_list_length (backend_ids));
for (; backend_ids; backend_ids = backend_ids->next) {
node = find_config_log_entry_backend (config_log,
start_node,
backend_ids->data);
if (!node)
id_array[i] = -1;
else
id_array[i] = ((ConfigLogEntry *) node->data)->id;
i++;
}
return id_array;
}
/* Return the rollback id that is the given number of steps back from the
* current revision, or -1 if there is no such id
*/
gint
config_log_get_rollback_id_by_steps (ConfigLog *config_log,
guint steps, gchar *backend_id)
{
GList *node;
g_return_val_if_fail (config_log != NULL, -1);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), -1);
g_return_val_if_fail (backend_id != NULL, -1);
node = config_log->log_data;
if (node == NULL)
node = load_next_log_entry (config_log, node);
while (node != NULL && steps-- > 0) {
node = find_config_log_entry_backend
(config_log, node, backend_id);
if (steps > 0) {
if (node->next == NULL)
node = load_next_log_entry (config_log, node);
else
node = node->next;
}
}
if (node != NULL)
return ((ConfigLogEntry *) node->data)->id;
else
return -1;
}
/* Return the backend that generated the data with the given id */
gchar *
config_log_get_backend_id_for_id (ConfigLog *config_log, gint id)
{
GList *node;
g_return_val_if_fail (config_log != NULL, NULL);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
g_return_val_if_fail (id >= 0, NULL);
if (config_log->log_data == NULL)
config_log->log_data =
load_next_log_entry (config_log, NULL);
node = find_config_log_entry_id (config_log,
config_log->log_data, id);
if (!node)
return NULL;
else
return ((ConfigLogEntry *) node->data)->backend_id;
}
/* Return the date the data with the given id was written */
struct tm *
config_log_get_date_for_id (ConfigLog *config_log, gint id)
{
GList *node;
g_return_val_if_fail (config_log != NULL, NULL);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
g_return_val_if_fail (id >= 0, NULL);
if (config_log->log_data == NULL)
config_log->log_data =
load_next_log_entry (config_log, NULL);
node = find_config_log_entry_id (config_log,
config_log->log_data, id);
if (!node)
return NULL;
else
return ((ConfigLogEntry *) node->data)->date;
}
gint
config_log_write_entry (ConfigLog *config_log, gchar *backend_id)
{
ConfigLogEntry *entry;
g_return_val_if_fail (config_log != NULL, -1);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), -1);
g_return_val_if_fail (backend_id != NULL, -1);
entry = g_new0 (ConfigLogEntry, 1);
entry->id = get_next_id (config_log);
entry->date = get_current_date ();
entry->backend_id = g_strdup (backend_id);
config_log->log_data = g_list_prepend (config_log->log_data, entry);
return entry->id;
}
/**
* config_log_iterate:
* @config_log:
* @callback:
* @data:
*
* Iterate through all log entries an invoke the given callback on each one,
* passing the id, date created, and backend id to it
**/
void
config_log_iterate (ConfigLog *config_log, ConfigLogIteratorCB callback,
gpointer data)
{
GList *node;
ConfigLogEntry *entry;
g_return_if_fail (config_log != NULL);
g_return_if_fail (IS_CONFIG_LOG (config_log));
g_return_if_fail (callback != NULL);
node = config_log->log_data;
while (node != NULL) {
entry = (ConfigLogEntry *) node->data;
if (callback (config_log, entry->id, entry->backend_id,
entry->date, node->data)) break;
if (node->next == NULL)
node = load_next_log_entry (config_log, node);
else
node = node->next;
}
}
/**
* config_log_reset_filenames:
* @config_log:
*
* Rereads the log's location data to determine filenames
**/
void
config_log_reset_filenames (ConfigLog *config_log)
{
g_return_if_fail (config_log != NULL);
g_return_if_fail (IS_CONFIG_LOG (config_log));
if (config_log->filename != NULL)
g_free (config_log->filename);
config_log->filename =
g_concat_dir_and_file (location_get_path
(config_log->location),
"config.log");
if (config_log->lock_filename != NULL)
g_free (config_log->lock_filename);
config_log->lock_filename =
g_concat_dir_and_file (location_get_path
(config_log->location),
"config.log.lock");
}
/* Find the config log entry with the id given, starting at the given
* node. Return a pointer to the node.
*/
static GList *
find_config_log_entry_id (ConfigLog *config_log, GList *start, gint id)
{
GList *last;
ConfigLogEntry *entry;
g_return_val_if_fail (config_log != NULL, NULL);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
g_return_val_if_fail (id >= 0, NULL);
if (!start) return NULL;
while (start != NULL) {
last = start;
entry = (ConfigLogEntry *) start->data;
if (entry->id == id)
return start;
else if (entry->id < id)
return NULL;
start = start->next;
}
while (1) {
start = load_next_log_entry (config_log, last);
if (start == NULL) return NULL;
entry = (ConfigLogEntry *) start->data;
if (entry->id == id)
return start;
else if (entry->id < id)
return NULL;
}
return NULL;
}
/* Find the first config log entry made prior to the given date,
* starting at the given node. Return a pointer to the node.
*/
static GList *
find_config_log_entry_date (ConfigLog *config_log, GList *start,
struct tm *date)
{
GList *last;
ConfigLogEntry *entry;
g_return_val_if_fail (config_log != NULL, NULL);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
g_return_val_if_fail (date != NULL, NULL);
if (!start) return NULL;
while (start != NULL) {
last = start;
entry = (ConfigLogEntry *) start->data;
if (time_geq (date, entry->date))
return start;
start = start->next;
}
while (1) {
start = load_next_log_entry (config_log, last);
if (start == NULL) return NULL;
entry = (ConfigLogEntry *) start->data;
if (time_geq (date, entry->date))
return start;
}
return NULL;
}
/* Find the first config log entry made by the given backend,
* starting at the given node. Return a pointer to the node.
*/
static GList *
find_config_log_entry_backend (ConfigLog *config_log, GList *start,
gchar *backend_id)
{
GList *last;
ConfigLogEntry *entry;
g_return_val_if_fail (config_log != NULL, NULL);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
g_return_val_if_fail (backend_id != NULL, NULL);
if (!start) return NULL;
while (start != NULL) {
last = start;
entry = (ConfigLogEntry *) start->data;
if (!strcmp (entry->backend_id, backend_id))
return start;
start = start->next;
}
while (1) {
start = load_next_log_entry (config_log, last);
if (start == NULL) return NULL;
entry = (ConfigLogEntry *) start->data;
if (!strcmp (entry->backend_id, backend_id))
return start;
}
return NULL;
}
static GList *
load_next_log_entry (ConfigLog *config_log, GList *last)
{
gchar *buffer, *backend_id;
ConfigLogEntry *entry;
gboolean success;
g_return_val_if_fail (config_log != NULL, NULL);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
if (!config_log->file || feof (config_log->file)) return NULL;
buffer = get_line (config_log->file);
entry = g_new0 (ConfigLogEntry, 1);
entry->date = g_new0 (struct tm, 1);
success = parse_line (buffer, &entry->id,
entry->date, &backend_id);
if (success) {
entry->backend_id = g_strdup (backend_id);
last = g_list_append (last, entry);
if (!config_log->log_data) {
config_log->log_data = last;
config_log->first_old = last;
}
return g_list_find (last, entry);
} else {
g_free (entry);
return NULL;
}
}
/* Read an entire line from the given file, returning a pointer to an
* allocated string. Strip the trailing newline from the line.
*/
static gchar *
get_line (FILE *file)
{
int buf_size = 0;
char *buf = NULL, *tmp = NULL;
size_t distance = 0, amt_read = 0;
g_return_val_if_fail (file != NULL, NULL);
while (amt_read == buf_size - distance) {
distance = tmp - buf;
if (distance >= buf_size) {
if (buf == NULL) {
buf_size = 1024;
buf = g_new (char, buf_size);
} else {
buf_size *= 2;
buf = g_renew (char, buf, buf_size);
}
tmp = buf + distance;
}
fgets (tmp, buf_size - distance, file);
amt_read = strlen (tmp);
tmp += amt_read;
}
if (tmp) *(tmp - 1) = '\0';
return buf;
}
/* Parse a line from the log file. All pointers must be valid.
*
* Note: backend just points to somewhere in buffer, so it becomes
* invalid the next time the buffer is overwritten. If there's a
* trailing newline, it is not chopped off.
*
* Returns TRUE on success and FALSE on parse error; if FALSE is
* returned, the values placed in the variables given are undefined.
*/
static gboolean
parse_line (char *buffer, int *id, struct tm *date, char **backend_id)
{
sscanf (buffer, "%x", id);
while (isxdigit (*buffer)) buffer++;
if (!isspace (*buffer) || !isdigit (*(buffer + 1))) return FALSE;
buffer++;
if (extract_number (&buffer, &date->tm_year, 4) == FALSE)
return FALSE;
if (extract_number (&buffer, &date->tm_mon, 2) == FALSE)
return FALSE;
if (extract_number (&buffer, &date->tm_mday, 2) == FALSE)
return FALSE;
date->tm_year -= 1900;
date->tm_mon--;
if (!isspace (*buffer) || !isdigit (*(buffer + 1))) return FALSE;
buffer++;
if (extract_number (&buffer, &date->tm_hour, 2) == FALSE)
return FALSE;
if (*buffer != ':') return FALSE; buffer++;
if (extract_number (&buffer, &date->tm_min, 2) == FALSE)
return FALSE;
if (*buffer != ':') return FALSE; buffer++;
if (extract_number (&buffer, &date->tm_sec, 2) == FALSE)
return FALSE;
if (!isspace (*buffer) || *(buffer + 1) == '\0') return FALSE;
buffer++;
*backend_id = buffer;
return TRUE;
}
/* Return TRUE if the first given struct tm is greater than or equal
* to the second given struct tm; FALSE otherwise
*/
static gboolean
time_geq (struct tm *time1, struct tm *time2)
{
if (time1->tm_year > time2->tm_year) return TRUE;
if (time1->tm_year < time2->tm_year) return FALSE;
if (time1->tm_mon > time2->tm_mon) return TRUE;
if (time1->tm_mon < time2->tm_mon) return FALSE;
if (time1->tm_mday > time2->tm_mday) return TRUE;
if (time1->tm_mday < time2->tm_mday) return FALSE;
if (time1->tm_hour > time2->tm_hour) return TRUE;
if (time1->tm_hour < time2->tm_hour) return FALSE;
if (time1->tm_min > time2->tm_min) return TRUE;
if (time1->tm_min < time2->tm_min) return FALSE;
if (time1->tm_sec >= time2->tm_sec) return TRUE;
return FALSE;
}
/* Opens up a configuration log. Assumes all the structures are
* already initialized. Creates the log if not already done.
*
* Returns TRUE on success and FALSE on failure (unable to open output
* file or log is locked)
*/
static gboolean
do_load (ConfigLog *config_log)
{
FILE *lock_file;
g_return_val_if_fail (config_log != NULL, FALSE);
g_return_val_if_fail (IS_CONFIG_LOG (config_log), FALSE);
g_return_val_if_fail (config_log->location != NULL, FALSE);
g_return_val_if_fail (IS_LOCATION (config_log->location), FALSE);
do_unload (config_log);
config_log_reset_filenames (config_log);
/* FIXME: Race condition here, plus lock handling should be
* better */
if (g_file_test (config_log->lock_filename, G_FILE_TEST_ISFILE))
return FALSE;
lock_file = fopen (config_log->lock_filename, "w");
fclose (lock_file);
config_log->file = fopen (config_log->filename, "r");
return TRUE;
}
/* Closes the input file for a given log and dumps and clears the
* cache
*/
static void
do_unload (ConfigLog *config_log)
{
GList *tmp;
g_return_if_fail (config_log != NULL);
g_return_if_fail (IS_CONFIG_LOG (config_log));
dump_log (config_log);
if (config_log->file) {
fclose (config_log->file);
config_log->file = NULL;
}
if (config_log->filename) {
g_free (config_log->filename);
config_log->filename = NULL;
}
if (config_log->lock_filename) {
unlink (config_log->lock_filename);
g_free (config_log->lock_filename);
config_log->lock_filename = NULL;
}
while (config_log->log_data) {
tmp = config_log->log_data->next;
config_log_entry_destroy
((ConfigLogEntry *) config_log->log_data->data);
g_list_free_1 (config_log->log_data);
config_log->log_data = tmp;
}
}
/* Returns the next id number in the sequence */
static gint
get_next_id (ConfigLog *config_log)
{
if (config_log->log_data == NULL) {
if (load_next_log_entry (config_log, NULL) == NULL)
return 0;
}
return ((ConfigLogEntry *) config_log->log_data->data)->id + 1;
}
/* Return a newly allocated struct tm with the current time */
static struct tm *
get_current_date (void)
{
time_t current_time;
struct tm *time_1, *ret;
current_time = time (NULL);
time_1 = gmtime (&current_time);
ret = g_new (struct tm, 1);
memcpy (ret, time_1, sizeof (struct tm));
return ret;
}
/* Write out a log entry */
static void
write_log (FILE *output, ConfigLogEntry *entry)
{
g_return_if_fail (output != NULL);
g_return_if_fail (entry != NULL);
g_return_if_fail (entry->id >= 0);
g_return_if_fail (entry->date != NULL);
g_return_if_fail (entry->backend_id != NULL);
fprintf (output, "%08x %04d%02d%02d %02d:%02d:%02d %s\n",
entry->id, entry->date->tm_year + 1900,
entry->date->tm_mon + 1, entry->date->tm_mday,
entry->date->tm_hour, entry->date->tm_min,
entry->date->tm_sec, entry->backend_id);
}
static void
dump_log (ConfigLog *config_log)
{
char *filename_out;
FILE *output;
GList *first;
char buffer[16384];
size_t size;
g_return_if_fail (config_log != NULL);
g_return_if_fail (IS_CONFIG_LOG (config_log));
g_return_if_fail (config_log->location != NULL);
g_return_if_fail (IS_LOCATION (config_log->location));
g_return_if_fail (location_get_path (config_log->location) != NULL);
filename_out = g_concat_dir_and_file (location_get_path
(config_log->location),
"config.log.out");
output = fopen (filename_out, "w");
if (!output) {
g_warning ("Could not open output file: %s",
g_strerror (errno));
return;
}
for (first = config_log->log_data; first != config_log->first_old;
first = first->next)
write_log (output, first->data);
if (config_log->file) {
rewind (config_log->file);
while (!feof (config_log->file)) {
size = fread (buffer, sizeof (char),
16384, config_log->file);
fwrite (buffer, sizeof (char), size, output);
}
}
fclose (output);
if (config_log->filename)
rename (filename_out, config_log->filename);
}
static void
config_log_entry_destroy (ConfigLogEntry *entry)
{
g_return_if_fail (entry != NULL);
g_return_if_fail (entry->date != NULL);
g_return_if_fail (entry->backend_id != NULL);
g_free (entry->date);
g_free (entry->backend_id);
g_free (entry);
}

92
archiver/config-log.h Normal file
View file

@ -0,0 +1,92 @@
/* -*- mode: c; style: linux -*- */
/* config-log.h
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen (hovinen@helixcode.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.
*/
#ifndef __CONFIG_LOG_H
#define __CONFIG_LOG_H
#include <gnome.h>
#include <stdio.h>
#include <time.h>
#define CONFIG_LOG(obj) GTK_CHECK_CAST (obj, config_log_get_type (), ConfigLog)
#define CONFIG_LOG_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, config_log_get_type (), ConfigLogClass)
#define IS_CONFIG_LOG(obj) GTK_CHECK_TYPE (obj, config_log_get_type ())
typedef struct _ConfigLog ConfigLog;
typedef struct _ConfigLogClass ConfigLogClass;
typedef struct _Location Location;
typedef gint (*ConfigLogIteratorCB) (ConfigLog *, gint, gchar *,
struct tm *, gpointer);
struct _ConfigLog
{
GtkObject object;
Location *location;
FILE *file;
char *filename;
char *lock_filename;
GList *log_data;
GList *first_old;
};
struct _ConfigLogClass
{
GtkObjectClass parent;
};
guint config_log_get_type (void);
GtkObject *config_log_open (Location *location);
void config_log_delete (ConfigLog *config_log);
gint config_log_get_rollback_id_for_date (ConfigLog *config_log,
struct tm *date,
gchar *backend_id);
gint *config_log_get_rollback_ids_for_date (ConfigLog *config_log,
struct tm *date,
GList *backend_ids);
gint config_log_get_rollback_id_by_steps (ConfigLog *config_log,
guint steps,
gchar *backend_id);
gchar *config_log_get_backend_id_for_id (ConfigLog *config_log,
gint id);
struct tm *config_log_get_date_for_id (ConfigLog *config_log,
gint id);
gint config_log_write_entry (ConfigLog *config_log,
gchar *backend_id);
void config_log_iterate (ConfigLog *config_log,
ConfigLogIteratorCB callback,
gpointer data);
void config_log_reset_filenames (ConfigLog *config_log);
#endif /* __CONFIG_LOG */

95
archiver/future-spec Normal file
View file

@ -0,0 +1,95 @@
Changes to the Helix Configuration Manager
Copyright (C) 2000 Helix Code, Inc.
Written by Bradford Hovinen <hovinen@helixcode.com>
As it stands, capplets and Helix Setup Tools are both run as separate
processes through the exec() facility. It is planned that the capplets
shall become Bonobo controls in the future, once the OAF/gnorba
compatibility problems are worked out. This changes the design of the
configuration system considerably, and several things should be done
to take full advantage of these changes.
1. Capplets become Bonobo controls
It stands to reason that the front ends for Helix Setup Tools should
become Bonobo controls at the same time as capplets. They can each
implement the same interface (say, Bonobo::Capplet) with methods
getXML(), setXML(), ok(), cancel(), and init() and hence look the same
to the shell. This means that the front ends for the Helix Setup Tools
run as the same user as HCM and respond in the same way as capplets do
to requests to embed them in the HCM shell window. This is essential
for a consistent user interface that will not result in end-user
confusion [1]. HCM itself may then export an interface that includes
the method runBackend(), to which the frontend supplies a stream of
XML that HCM passes through the root manager to the backend via a
standard pipe [2]. The backend is then responsible for running the
program that archives the XML -- there is no other way to place that
XML in a central, system-wide repository. I suggest, therefore, that
we modify the design of the current system to make that change, so
that we do not have to undo existing work later.
2. Backends get CORBA interfaces through Perl/ORBit
At this point, there must be a way for the root manager to forward
CORBA sockets to the user securely. This could be done by modifying
ORBit so as to give the running program very precise control over the
nature of the socket. Access could be granted specifically to the user
running the root manager by placing the socket in a directory owned by
that user with permissions no more lax than 0700. When the CORBA
interfaces are created, applications will be able to make use of it to
make system-wide changes as necessary (say, to add a new user during
the installation of a piece of software). This means that the
traditional rollback facilities must be extended to allow users to
roll back changes made by applications. In addition, the application
must treat the backend as a black box -- it should never be expected
to do anything unusual to support rollback, since buggy or
poorly-written applications would otherwise cause trouble for
unsuspecting users.
At this point I suggest that each backend export two interfaces: one
that is universal to all backends and one that is specific to that
particular tool. The former may include the methods getXML(),
setXML(), and commit(). When changes are made through the
tool-specific interface, the tool decides whether or not to apply
those changes immediately or to queue them up until a commit() is
invoked. If changes are made through the backend's CORBA interface and
it is deactivated before a commit(), the backend must roll back those
changes under the assumption that they are not intended to be
permanent.
Of course, this makes implementation of the cancel() interface on the
frontends very easy -- simply deactivate the backend without
commit()ing. ok() can be implemented by flushing any remaining
changes, calling commit(), and then deactivating the backend. The
frontend can and should use the CORBA interface to invoke changes
whenever they are made, as long as it makes sense. It is then the
backend that sets the policy of whether or not the updates are live,
as described above. The frontend must still be able to read XML,
though, since it is through that that it will get an initial
description of the setup with which to fill in the dialog. In
addition, since the frontend may be invoked to make changes to an
inactive location, it should be able to write out an XML description
of the dialog's contents so that those changes may be archived rather
than applied immediately.
Notes
[1] A visual cue that signals to the user that he is running a
system-wide configuration tool rather than a personal one would be
advantageous. Such could take the form of an icon on the dialog, a
layout or formatting convention for the dialog proper, or some sort of
coloring convention of some of the controls. However, simply having
the tool run with root's Gtk+ theme and ignoring the embedding
preference, as would be the case if we do not Bonobize the HST
frontends, is inconsistent as many users will leave their themes as
the default and elect not to embed capplets -- eliminating all visual
cues. In addition, it is not particularly lucid and many users will
merely be confused by the inconsistent interface. One may imagine many
users filing bug reports in the belief that the behavior is
erroneous. Hence, that solution is insufficient.
[2] There must then be a method of multiplexing I/O throught the root
manager, as there may be multiple backends running concurrently. A
simple protocol could be implemented to do this, or a named pipe could
be created if done very carefully as to ensure a high degree of
security.

1232
archiver/location.c Normal file

File diff suppressed because it is too large Load diff

112
archiver/location.h Normal file
View file

@ -0,0 +1,112 @@
/* -*- mode: c; style: linux -*- */
/* location.h
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen (hovinen@helixcode.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.
*/
#ifndef __LOCATION_H
#define __LOCATION_H
#include <gnome.h>
#include "config-log.h"
#define LOCATION(obj) GTK_CHECK_CAST (obj, location_get_type (), Location)
#define LOCATION_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, location_get_type (), LocationClass)
#define IS_LOCATION(obj) GTK_CHECK_TYPE (obj, location_get_type ())
typedef struct _LocationClass LocationClass;
typedef struct _LocationPrivate LocationPrivate;
typedef struct _Archive Archive;
typedef int (*LocationBackendCB) (Location *, gchar *, gpointer);
struct _Location
{
GtkObject object;
LocationPrivate *p;
};
struct _LocationClass
{
GtkObjectClass parent;
};
guint location_get_type (void);
GtkObject *location_new (Archive *archive,
const gchar *locid,
Location *inherits);
GtkObject *location_open (Archive *archive,
const gchar *locid);
void location_close (Location *location);
void location_delete (Location *location);
void location_store (Location *location,
gchar *backend_id,
FILE *input);
void location_rollback_backend_to (Location *location,
struct tm *date,
gchar *backend_id,
gboolean parent_chain);
void location_rollback_backends_to (Location *location,
struct tm *date,
GList *backends,
gboolean parent_chain);
void location_rollback_all_to (Location *location,
struct tm *date,
gboolean parent_chain);
void location_rollback_backend_by (Location *location,
guint steps,
gchar *backend_id,
gboolean parent_chain);
void location_rollback_id (Location *location,
gint id);
void location_dump_rollback_data (Location *location,
struct tm *date,
guint steps,
gchar *backend_id,
gboolean parent_chain,
FILE *output);
gboolean location_contains (Location *location, gchar *backend_id);
gint location_add_backend (Location *location, gchar *backend_id);
void location_remove_backend (Location *location, gchar *backend_id);
void location_foreach_backend (Location *location,
LocationBackendCB callback,
gpointer data);
GList *location_find_path_from_common_parent (Location *location,
Location *location2);
const gchar *location_get_path (Location *location);
const gchar *location_get_label (Location *location);
const gchar *location_get_id (Location *location);
void location_set_id (Location *location, const gchar *locid);
#endif /* __LOCATION */

316
archiver/main.c Normal file
View file

@ -0,0 +1,316 @@
/* -*- mode: c; style: linux -*- */
/* main.c
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen (hovinen@helixcode.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 <config.h>
#endif
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <sys/file.h>
#include <unistd.h>
#include <errno.h>
#include <gnome.h>
#include "util.h"
#include "archive.h"
/* Variables resulting from command line parsing */
static gboolean store;
static gboolean rollback;
static gboolean change_location;
static gboolean rename_location;
static gboolean push_config;
static gboolean add_location;
static gboolean remove_location;
static gboolean add_backend;
static gboolean remove_backend;
static gboolean global;
static const gchar *location_id;
static gchar *backend_id;
static gchar *date_str;
static gboolean all;
static gchar *revision_id;
static gboolean last;
static guint steps;
static gboolean show;
static gchar *parent_str;
static gchar *new_name;
static gboolean master;
static struct poptOption archiver_operations[] = {
{"store", 's', POPT_ARG_NONE, &store, 0,
N_("Store XML data in the archive")},
{"rollback", 'r', POPT_ARG_NONE, &rollback, 0,
N_("Roll back the configuration to a given point")},
{"change-location", 'c', POPT_ARG_NONE, &change_location, 0,
N_("Change the location profile to the given one")},
{"push-config", 'p', POPT_ARG_NONE, &push_config, 0,
N_("Push configuration data out to client machines (UNIMPLEMENTED)")},
{"rename-location", '\0', POPT_ARG_NONE, &rename_location, 0,
N_("Rename a location to a new name")},
{"add-location", '\0', POPT_ARG_NONE, &add_location, 0,
N_("Add a new location to the archive")},
{"remove-location", '\0', POPT_ARG_NONE, &remove_location, 0,
N_("Remove a location from the archive")},
{"add-backend", '\0', POPT_ARG_NONE, &add_backend, 0,
N_("Add a given backend to the given location")},
{"remove-backend", '\0', POPT_ARG_NONE, &remove_backend, 0,
N_("Remove the given backend from the given location")},
{NULL, '\0', 0, NULL, 0}
};
static struct poptOption global_options[] = {
{"global", 'g', POPT_ARG_NONE, &global, 0,
N_("Use the global repository")},
{"location", 'l', POPT_ARG_STRING, &location_id, 0,
N_("Identifier of location profile on which to operate"),
N_("LOCATION")},
{"backend", 'b', POPT_ARG_STRING, &backend_id, 0,
N_("Backend being used for this operation"), N_("BACKEND_ID")},
{NULL, '\0', 0, NULL, 0}
};
static struct poptOption rollback_options[] = {
{"date", 'd', POPT_ARG_STRING, &date_str, 0,
N_("Date to which to roll back"), N_("DATE")},
{"all", 'a', POPT_ARG_NONE, &all, 0,
N_("Roll back all configuration items")},
{"revision-id", 'i', POPT_ARG_INT, &revision_id, 0,
N_("Roll back to the revision REVISION_ID"), N_("REVISION_ID")},
{"last", 't', POPT_ARG_NONE, &last, 0,
N_("Roll back to the last known revision")},
{"steps", '\0', POPT_ARG_INT, &steps, 0,
N_("Roll back by STEPS revisions"), N_("STEPS")},
{"show", 'h', POPT_ARG_NONE, &show, 0,
N_("Don't run the backend, just dump the output")},
{NULL, '\0', 0, NULL, 0}
};
static struct poptOption add_rename_location_options[] = {
{"parent", '\0', POPT_ARG_STRING, &parent_str, 0,
N_("Parent location for the new location"), N_("PARENT")},
{"new-name", '\0', POPT_ARG_STRING, &new_name, 0,
N_("New name to assign to the location"), N_("NEW_NAME")},
{NULL, '\0', 0, NULL, 0}
};
static struct poptOption add_remove_backend_options[] = {
{"master", '\0', POPT_ARG_NONE, &master, 0,
N_("Add/remove this backend to/from the master backend list")},
{NULL, '\0', 0, NULL, 0}
};
static void
do_store (Location *location)
{
if (!backend_id) {
g_message ("No backend specified");
return;
}
location_store (location, backend_id, stdin);
}
static void
do_rollback (Location *location)
{
gint id;
struct tm *date = NULL;
if (date_str)
date = parse_date (date_str);
else if (last || steps > 0)
date = NULL;
else if (!revision_id) {
g_message ("No date specified");
return;
}
if (all) {
location_rollback_all_to (location, date, TRUE);
}
else if (backend_id && (date || last)) {
/* FIXME: Need to support specifying multiple backends */
if (show)
location_dump_rollback_data (location, date, 0,
backend_id, TRUE, stdout);
else
location_rollback_backend_to (location, date,
backend_id, TRUE);
}
else if (backend_id && steps) {
if (show)
location_dump_rollback_data (location, NULL, steps,
backend_id, TRUE, stdout);
else
location_rollback_backend_by (location, steps,
backend_id, TRUE);
}
else if (revision_id) {
sscanf (revision_id, "%x", &id);
if (id >= 0)
location_rollback_id (location, id);
else
g_message ("Bad id specified");
} else {
g_message ("No backend specified");
return;
}
}
static void
do_change_location (Archive *archive, Location *location)
{
archive_set_current_location (archive, location);
}
static void
do_rename_location (Archive *archive, Location *location)
{
gboolean is_current;
if (new_name == NULL) {
g_message ("You did not specify a new name. Try --help");
} else {
if (!strcmp (location_get_id (location),
archive_get_current_location_id (archive)))
is_current = TRUE;
else
is_current = FALSE;
location_set_id (location, new_name);
if (is_current)
archive_set_current_location_id (archive, new_name);
}
}
static void
do_add_location (Archive *archive)
{
GtkObject *location;
Location *parent_location = NULL;
if (parent_str != NULL)
parent_location = archive_get_location (archive, parent_str);
location = location_new (archive, location_id, parent_location);
}
static void
do_remove_location (Location *location)
{
location_delete (location);
}
static void
do_add_backend (Location *location)
{
location_add_backend (location, backend_id);
}
static void
do_remove_backend (Location *location)
{
location_remove_backend (location, backend_id);
}
int
main (int argc, char **argv)
{
Archive *archive;
Location *location = NULL;
bindtextdomain (PACKAGE, GNOMELOCALEDIR);
textdomain (PACKAGE);
gnomelib_register_popt_table (global_options,
_("Global archiver options"));
gnomelib_register_popt_table (archiver_operations,
_("Archiver commands"));
gnomelib_register_popt_table (rollback_options,
_("Options for rolling back"));
gnomelib_register_popt_table (add_rename_location_options,
_("Options for adding or renaming " \
"locations"));
gnomelib_register_popt_table (add_remove_backend_options,
_("Options for adding and removing " \
"backends"));
gtk_type_init ();
gnomelib_init ("archiver", VERSION);
gnomelib_parse_args (argc, argv, 0);
archive = ARCHIVE (archive_load (global));
if (archive == NULL)
g_error ("Could not open archive");
if (location_id == NULL)
location_id = archive_get_current_location_id (archive);
if (!add_location) {
location = archive_get_location (archive, location_id);
if (location == NULL) {
if (strcmp (location_id, "default")) {
g_message ("Could not open location");
return -1;
} else {
location = LOCATION
(location_new (archive, location_id,
NULL));
}
}
}
if (store)
do_store (location);
else if (rollback)
do_rollback (location);
else if (change_location)
do_change_location (archive, location);
else if (rename_location)
do_rename_location (archive, location);
else if (add_location)
do_add_location (archive);
else if (remove_location)
do_remove_location (location);
else if (add_backend)
do_add_backend (location);
else if (remove_backend)
do_remove_backend (location);
archive_close (archive);
return 0;
}

97
archiver/util.c Normal file
View file

@ -0,0 +1,97 @@
/* -*- mode: c; style: linux -*- */
/* util.c
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen (hovinen@helixcode.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 <config.h>
#endif
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
/* Read a fixed-digit number from a string, advancing the string
* pointer. Return TRUE if the extraction was successful, FALSE if
* there was no number to extract or if the number was too short.
*/
gboolean
extract_number (char **str, int *number, int digits)
{
char buf[64];
if (!isdigit (**str)) return FALSE;
if (digits > 63) digits = 63;
strncpy (buf, *str, digits);
buf[digits] = '\0';
*number = atoi (buf);
if (strlen (buf) < digits) return FALSE;
*str += digits;
return TRUE;
}
struct tm *
parse_date (char *str)
{
struct tm *date;
gboolean ok;
gint value;
ok = extract_number (&str, &value, 4);
if (!ok) return NULL;
date = g_new (struct tm, 1);
date->tm_year = value;
date->tm_mon = 12;
date->tm_mday = 31;
date->tm_hour = 23;
date->tm_min = 59;
date->tm_sec = 59;
if (extract_number (&str, &value, 2))
date->tm_mon = value;
else
return date;
if (extract_number (&str, &value, 2))
date->tm_mday = value;
else
return date;
if (extract_number (&str, &value, 2))
date->tm_hour = value;
else
return date;
if (extract_number (&str, &value, 2))
date->tm_min = value;
else
return date;
if (extract_number (&str, &value, 2))
date->tm_sec = value;
return date;
}

33
archiver/util.h Normal file
View file

@ -0,0 +1,33 @@
/* -*- mode: c; style: linux -*- */
/* util.h
* Copyright (C) 2000 Helix Code, Inc.
*
* Written by Bradford Hovinen (hovinen@helixcode.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.
*/
#ifndef __UTIL_H
#define __UTIL_H
#include <time.h>
#include <glib.h>
gboolean extract_number (char **str, int *number, int digits);
struct tm *parse_date (char *str);
#endif /* __UTIL_H */

193
archiver/versioning-spec Normal file
View file

@ -0,0 +1,193 @@
Configuration rollback, location management, and cluster support
Copyright (C) 2000 Helix Code, Inc.
Written by Bradford Hovinen <hovinen@helixcode.com>
I. Basic architecture
A. Components
1. Helix Configuration Manager
The GUI shell, here referred to as the Helix Configuration Manager
(HCM), acts as launching point for the capplets and Helix Setup Tools,
and a control center for managing rollback, location management, and
clustering. It launches other components and knows how to find
archived configuration data for rollback. When rollback or a change of
location is required, it invokes the required backends or capplets
with the --set option and feeds the required XML to them.
2. Capplets
Capplets handle user configuration; they combine the front and back
ends into one process. They will eventually run as Bonobo controls
implementing the Bonobo::Capplet interface, but for now they are run
as regular processes. They all support the --get and --set command
line options (which will be methods in the Bonobo::Capplet
interface). --get returns an XML description of the capplet's state
for archival purposes and --set takes an XML description of the
capplet's state and applies those settings to the desktop.
3. Helix Setup Tools (HSTs)
These programs are for system-wide configuration and must run as root
in order to apply changes. They may also run as a regular user in
`read-only' mode. They have separate front- and backends, the former
typically written in C and the latter normally written in Perl. This
facilitates in-place modification of existing configuration files
without the need for creating out own separate, incompatible way of
configuring the system. The backends support the --get and --set
arguments, analogous to the arguments in capplets mentioned above.
2. Root manager
The root manager process runs as root and is launched through
gnome-su. It accepts on stdin a set of programs to launch, one per
line, with command line arguments. HCM uses it to launch Helix Setup
Tools so that they run as root, without needing to ask the user for a
password each time a tool is run. The root manager is run exactly once
through console-helper the first time a tool that must be run as root
is invoked. On subsequent occasions the command to start the tool is
passed to the root manager through stdin.
3. The script do-changes
do-changes is responsible for archiving changes made to the system's
configuration and passing them on to the backend, if appropriate. It
accepts a stream of XML on stdin and stores this XML in the
configuration archive directory. If a backend is specified on the
command line, it also spawns the backend process with the --set option
and feeds the XML to it through stdin.
II. Configuration process
When a user makes changes to either his own configuration or that of
the system, those changes must be archived so that the system may be
rolled back in the future. In the case of capplets, the capplet
currently dumps an XML snapshot of its current state to the script
do-changes when the user clicks `Ok'. do-changes then archives the
state in ~/.gnome/config/<location>/<revision> where <location> is the
name of the active location (cf. section IV) and <revision> is
incremented after each change.
When the capplets are converted into Bonobo controls, the situation
will be slightly different. HCM will be the recipient of the `Ok'
signal, so it will invoke the OKClicked() method of the
Bonobo::Capplet interface on the appropriate capplet. It will also
invoke the GetXML() method of the same interface in order to retrieve
an XML snapshot of the state and store that snapshot with
do-changes. Hence, much of the action moves from the capplet to HCM.
In the case of Helix Setup Tools, the frontend passes the XML through
the do-changes script to the backend whenever the `Ok' button is
clicked. It passes to do-changes the argument --backend <backend name>
so that do-changes will also invoke the indicated backend and pass the
XML to it.
III. Rollback process
From within the HCM, the user may elect to roll back either his
personal configuration or that of the system to a particular
date. HCM looks for a revision directory in the current location
profile with the most recent modification date that is not more recent
than the date specified by the user. HCM also has a list of what
capplets (or HSTs) constitute a complete snapshot of the system's
configuration. In order to perform a complete rollback, it backtracks
through the revision directories, picking up XML snapshots of capplets
until it has a complete set and applies them through the --set
method. In the case of HSTs, the HCM knows how to invoke the backend
and does so as necessary.
IV. Location management
The system may have one or more profiles, each giving different system
configurations. For example, a user may own a laptop and wish to hook
that laptop up to different networks at different times. Each network
may be located in a different time zone, have different network
configuration parameters (e.g., DHCP vs. static IPs), and use different
default printers. When the user hooks his laptop up in a particular
network, it would be advantageous to switch to that network's
configuration with a minimum of hassle.
As mentioned above, configuration data is stored in separate
directories corresponding to the name of a given location. HCM has the
ability to apply a set of configuration files from a given location in
a manner similar to the rollback procedure described above. When the
user selects an alternative configuration, it simply goes through the
revision history for that location, pulls a complete set of
configuration files, and applies them. The procedure is similar for
both capplets and HSTs.
In addition, locations may be expressed hierarchically. For example, a
user might specify a location called `Boston' that describes language,
time zone, currency, and other locale data, and another location called
`Boston Office' that includes network parameters. `Boston Office'
inherits its locale data from `Boston', overriding the latter's
network configuration.
To implement this, each location directory contains some metadata that
describes what configuration data is valid for it and what other
configuration it inherits from. There are one or more root
configurations that contain a complete set of data. When applying a
new location, HCM looks first at that location's directory, pulling a
complete set of all the configuration data defined by that location,
and then goes to the next level up in the location hierarchy and does
the same thing. It also keeps track of the common subtree root between
the old and new locations so that only the configuration items that
actually change are collected.
From a user's perspective, the HCM will present a tree showing the
existing locations. Users may create a new location derived from an
existing one. When the user elects to configure a particular location,
the HCM shell includes icons that are grayed out, indicating that
those configuration items are not set in this particular location. If
the user attempts to change them, they become specific to that
particular location and are recolored accordingly.
V. Clustering
A single server may archive the configuration for a large number of
individual workstations, providing configuration data to each of the
clients on demand. An administrator can then push configuration
updates out to each machine with the press of a button, rather than
having to go to each machine and update it manually.
To enable this, each client machine will run a daemon that accepts
configuration data pushed out by the server. Some sort of public key
signing will be implemented to ensure that this is done securely. On
the server end, a series of host groups is maintained, each one
containing a set of hosts. These form the top two levels of a
configuration hierarchy not unlike what is described above. Each host
may override certain configuration values for the cluster as a
whole. The cluster may also have multiple `locations', e.g. for
configuring a computer lab for computer science during one class and
for math during another. Locations may be selected down to the
granularity of a single host, or for the entire cluster at
once. Cluster-wide configurations occur between the cluster and host
level in the configuration hierarchy.
VI. Issues
1. We need a way to get an XML state without actually applying
changes, so that the user can configure a location without switching
to it.
2. Can we make the HST frontends Bonobo controls, and can we have them
run as the regular user rather than as root? This would ensure that
certain user interface preferences, such as themes, are kept
consistent for a given user between capplets and HSTs. The way to
implement this is to have a method on the HCM interface called
RunBackend() which returns a BonoboObject referring to the backend
that implements the Bonobo::HSTBackend interface, which is similar to
the Bonobo::Capplet interface mentioned above. The interface defines
the GetXML and SetXML methods. The object should also implement
another, HST-specific interface to facilitate setting specific
configuration variables, so that live update may be implemented. The
root manager must then be extended to support some sort of secure
forwarding, allowing the user to access that particular CORBA object.
3. If we make the HSTs into Bonobo controls, can we give them the same
Bonobo::Capplet interface that is given to the Capplets? This would
make everything a bit simpler from the HCM's perspective, since it
then does not need to know the difference between Capplets and
HSTs -- it then only needs to implement the RunBackend() method for
the benefit of the HSTs.