diff options
author | Stef Walter <stefw@redhat.com> | 2014-08-30 07:32:23 +0200 |
---|---|---|
committer | Stef Walter <stefw@redhat.com> | 2014-09-13 08:32:03 +0200 |
commit | 8c9c1a8fcadff500bc39f71acb411763e7afbe3f (patch) | |
tree | 1cc99113c7cfe0ffffecb16de042f8331b32ee07 /egg | |
parent | 1dedc7c174c65f9e1680e19b53c0f44295bd03c5 (diff) | |
download | gnome-keyring-8c9c1a8fcadff500bc39f71acb411763e7afbe3f.tar.gz |
egg: Move file tracker code to egg/ directory
So that it can be used in other parts of gnome-keyring.
Diffstat (limited to 'egg')
-rw-r--r-- | egg/Makefile.am | 6 | ||||
-rw-r--r-- | egg/egg-file-tracker.c | 303 | ||||
-rw-r--r-- | egg/egg-file-tracker.h | 59 | ||||
-rw-r--r-- | egg/test-file-tracker.c | 251 |
4 files changed, 619 insertions, 0 deletions
diff --git a/egg/Makefile.am b/egg/Makefile.am index f4893578..1e3bc889 100644 --- a/egg/Makefile.am +++ b/egg/Makefile.am @@ -25,6 +25,7 @@ libegg_la_SOURCES = \ egg/egg-dh.c egg/egg-dh.h \ egg/egg-dn.c egg/egg-dn.h \ egg/egg-error.h \ + egg/egg-file-tracker.c egg/egg-file-tracker.h \ egg/egg-hex.c egg/egg-hex.h \ egg/egg-hkdf.c egg/egg-hkdf.h \ egg/egg-libgcrypt.c egg/egg-libgcrypt.h \ @@ -126,6 +127,7 @@ egg_LIBS = \ libegg.la \ $(LIBGCRYPT_LIBS) \ $(GTHREAD_LIBS) \ + $(GOBJECT_LIBS) \ $(GLIB_LIBS) egg_TESTS = \ @@ -142,6 +144,7 @@ egg_TESTS = \ test-armor \ test-openssl \ test-dh \ + test-file-tracker \ test-spawn test_asn1_SOURCES = egg/test-asn1.c egg/test.asn.h @@ -156,6 +159,9 @@ test_dn_LDADD = $(egg_LIBS) test_cleanup_SOURCES = egg/test-cleanup.c test_cleanup_LDADD = $(egg_LIBS) +test_file_tracker_SOURCES = egg/test-file-tracker.c +test_file_tracker_LDADD = $(egg_LIBS) + test_hex_SOURCES = egg/test-hex.c test_hex_LDADD = $(egg_LIBS) diff --git a/egg/egg-file-tracker.c b/egg/egg-file-tracker.c new file mode 100644 index 00000000..0dbfbef6 --- /dev/null +++ b/egg/egg-file-tracker.c @@ -0,0 +1,303 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* egg-file-tracker.c - Watch for changes in a directory + + Copyright (C) 2008 Stefan Walter + + The Gnome Keyring Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Keyring Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + <http://www.gnu.org/licenses/>. + + Author: Stef Walter <stef@memberwebs.com> +*/ + +#include "config.h" + +#include "egg-file-tracker.h" + +#include "egg/egg-error.h" + +#include <glib.h> +#include <glib/gstdio.h> + +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> + +typedef struct _UpdateDescendants { + EggFileTracker *tracker; + GHashTable *checks; +} UpdateDescendants; + +struct _EggFileTracker { + GObject parent; + + /* Specification */ + GPatternSpec *include; + GPatternSpec *exclude; + gchar *directory_path; + time_t directory_mtime; + + /* Matched files */ + GHashTable *files; +}; + +enum { + FILE_ADDED, + FILE_REMOVED, + FILE_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (EggFileTracker, egg_file_tracker, G_TYPE_OBJECT); + +/* ----------------------------------------------------------------------------- + * HELPERS + */ + +static void +copy_key_string (gpointer key, gpointer value, gpointer data) +{ + GHashTable *dest = (GHashTable*)data; + g_hash_table_replace (dest, g_strdup (key), value); +} + +static void +remove_files (gpointer key, gpointer value, gpointer data) +{ + EggFileTracker *self = EGG_FILE_TRACKER (data); + + g_hash_table_remove (self->files, key); + g_signal_emit (self, signals[FILE_REMOVED], 0, key); +} + +static gboolean +update_file (EggFileTracker *self, gboolean force_all, const gchar *path) +{ + time_t old_mtime; + struct stat sb; + + if (stat (path, &sb) < 0) { + if (errno != ENOENT && errno != ENOTDIR && errno != EPERM) + g_warning ("couldn't stat file: %s: %s", path, g_strerror (errno)); + return FALSE; + } + + old_mtime = GPOINTER_TO_UINT (g_hash_table_lookup (self->files, path)); + g_assert (old_mtime); + + /* See if it has actually changed */ + if (force_all || old_mtime != sb.st_mtime) { + g_assert (g_hash_table_lookup (self->files, path)); + g_hash_table_insert (self->files, g_strdup (path), GUINT_TO_POINTER (sb.st_mtime)); + g_signal_emit (self, signals[FILE_CHANGED], 0, path); + } + + return TRUE; +} + +static void +update_each_file (gpointer key, gpointer unused, gpointer data) +{ + UpdateDescendants *ctx = (UpdateDescendants*)data; + if (update_file (ctx->tracker, FALSE, key)) + g_hash_table_remove (ctx->checks, key); +} + +static void +update_directory (EggFileTracker *self, gboolean force_all, GHashTable *checks) +{ + UpdateDescendants uctx; + struct stat sb; + GError *err = NULL; + const char *filename; + gchar *file; + GDir *dir; + int ret, lasterr; + + g_assert (checks); + g_assert (EGG_IS_FILE_TRACKER (self)); + + if (!self->directory_path) + return; + + if (stat (self->directory_path, &sb) < 0) { + if (errno != ENOENT && errno != ENOTDIR && errno != EPERM) + g_message ("couldn't stat directory: %s: %s", + self->directory_path, g_strerror (errno)); + return; + } + + /* See if it was updated since last seen or not */ + if (!force_all && self->directory_mtime == sb.st_mtime) { + + uctx.checks = checks; + uctx.tracker = self; + + /* Still need to check for individual file updates */ + g_hash_table_foreach (self->files, update_each_file, &uctx); + return; + } + + self->directory_mtime = sb.st_mtime; + + /* Actually list the directory */ + dir = g_dir_open (self->directory_path, 0, &err); + if (dir == NULL) { + if (errno != ENOENT && errno != ENOTDIR && errno != EPERM) + g_message ("couldn't list keyrings at: %s: %s", self->directory_path, + egg_error_message (err)); + g_error_free (err); + return; + } + + while ((filename = g_dir_read_name (dir)) != NULL) { + if (filename[0] == '.') + continue; + if (self->include && !g_pattern_match_string (self->include, filename)) + continue; + if (self->exclude && g_pattern_match_string (self->exclude, filename)) + continue; + + file = g_build_filename (self->directory_path, filename, NULL); + + /* If we hadn't yet seen this, then add it */ + if (!g_hash_table_remove (checks, file)) { + + /* Get the last modified time for this one */ + ret = g_stat (file, &sb); + lasterr = errno; + + /* Couldn't access the file */ + if (ret < 0) { + g_message ("couldn't stat file: %s: %s", file, g_strerror (lasterr)); + + } else { + + /* We don't do directories */ + if (!(sb.st_mode & S_IFDIR)) { + g_hash_table_replace (self->files, g_strdup (file), GINT_TO_POINTER (sb.st_mtime)); + g_signal_emit (self, signals[FILE_ADDED], 0, file); + } + } + + /* Otherwise we already had it, see if it needs updating */ + } else { + update_file (self, force_all, file); + } + + g_free (file); + } + + g_dir_close (dir); +} + +/* ----------------------------------------------------------------------------- + * OBJECT + */ + +static void +egg_file_tracker_init (EggFileTracker *self) +{ + self->files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); +} + +static void +egg_file_tracker_finalize (GObject *obj) +{ + EggFileTracker *self = EGG_FILE_TRACKER (obj); + + if (self->include) + g_pattern_spec_free (self->include); + if (self->exclude) + g_pattern_spec_free (self->exclude); + g_free (self->directory_path); + + g_hash_table_destroy (self->files); + + G_OBJECT_CLASS (egg_file_tracker_parent_class)->finalize (obj); +} + +static void +egg_file_tracker_class_init (EggFileTrackerClass *klass) +{ + GObjectClass *gobject_class; + gobject_class = (GObjectClass*) klass; + + egg_file_tracker_parent_class = g_type_class_peek_parent (klass); + gobject_class->finalize = egg_file_tracker_finalize; + + signals[FILE_ADDED] = g_signal_new ("file-added", EGG_TYPE_FILE_TRACKER, + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_added), + NULL, NULL, g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[FILE_CHANGED] = g_signal_new ("file-changed", EGG_TYPE_FILE_TRACKER, + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_changed), + NULL, NULL, g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[FILE_REMOVED] = g_signal_new ("file-removed", EGG_TYPE_FILE_TRACKER, + G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_removed), + NULL, NULL, g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); +} + +EggFileTracker* +egg_file_tracker_new (const gchar *directory, const gchar *include, const gchar *exclude) +{ + EggFileTracker *self; + const gchar *homedir; + + g_return_val_if_fail (directory, NULL); + + self = g_object_new (EGG_TYPE_FILE_TRACKER, NULL); + + /* TODO: Use properties */ + + if (directory[0] == '~' && directory[1] == '/') { + homedir = g_getenv ("HOME"); + if (!homedir) + homedir = g_get_home_dir (); + self->directory_path = g_build_filename (homedir, directory + 2, NULL); + + /* A relative or absolute path */ + } else { + self->directory_path = g_strdup (directory); + } + + self->include = include ? g_pattern_spec_new (include) : NULL; + self->exclude = exclude ? g_pattern_spec_new (exclude) : NULL; + + return self; +} + +void +egg_file_tracker_refresh (EggFileTracker *self, gboolean force_all) +{ + GHashTable *checks; + + g_return_if_fail (EGG_IS_FILE_TRACKER (self)); + + /* Copy into our check set */ + checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_hash_table_foreach (self->files, copy_key_string, checks); + + /* If only one volume, then just try and access it directly */ + update_directory (self, force_all, checks); + + /* Find any keyrings whose paths we didn't see */ + g_hash_table_foreach (checks, remove_files, self); + g_hash_table_destroy (checks); +} diff --git a/egg/egg-file-tracker.h b/egg/egg-file-tracker.h new file mode 100644 index 00000000..9ce2f9b9 --- /dev/null +++ b/egg/egg-file-tracker.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* egg-file-tracker.h - Watch for changes in a directory + + Copyright (C) 2008, Stefan Walter + + The Gnome Keyring Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Keyring Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + <http://www.gnu.org/licenses/>. + + Author: Stef Walter <stef@memberwebs.com> +*/ + +#ifndef __EGG_FILE_TRACKER_H__ +#define __EGG_FILE_TRACKER_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define EGG_TYPE_FILE_TRACKER (egg_file_tracker_get_type ()) +#define EGG_FILE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_FILE_TRACKER, EggFileTracker)) +#define EGG_FILE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_FILE_TRACKER, GObject)) +#define EGG_IS_FILE_TRACKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_FILE_TRACKER)) +#define EGG_IS_FILE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_FILE_TRACKER)) +#define EGG_FILE_TRACKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_FILE_TRACKER, EggFileTrackerClass)) + +typedef struct _EggFileTracker EggFileTracker; +typedef struct _EggFileTrackerClass EggFileTrackerClass; + +struct _EggFileTrackerClass { + GObjectClass parent_class; + + void (*file_added) (EggFileTracker *locmgr, const gchar *path); + void (*file_changed) (EggFileTracker *locmgr, const gchar *path); + void (*file_removed) (EggFileTracker *locmgr, const gchar *path); +}; + +GType egg_file_tracker_get_type (void) G_GNUC_CONST; + +EggFileTracker* egg_file_tracker_new (const gchar *directory, + const gchar *include_pattern, + const gchar *exclude_pattern); + +void egg_file_tracker_refresh (EggFileTracker *self, + gboolean force_all); + +G_END_DECLS + +#endif /* __EGG_FILE_TRACKER_H__ */ diff --git a/egg/test-file-tracker.c b/egg/test-file-tracker.c new file mode 100644 index 00000000..926b4485 --- /dev/null +++ b/egg/test-file-tracker.c @@ -0,0 +1,251 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* test-file-tracker.c: File tracker tests + + Copyright (C) 2007 Stefan Walter + + The Gnome Keyring Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Keyring Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + <http://www.gnu.org/licenses/>. + + Author: Stef Walter <stef@memberwebs.com> +*/ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "egg/egg-file-tracker.h" + +#include <glib/gstdio.h> + +#define DATA "test-data" +#define SUBDIR "test-subdir" +#define WILDCARD "*.woo?" + +typedef struct { + EggFileTracker *the_tracker; + gchar *test_dir; + gchar *test_file; + guint n_files_added; + gchar* last_file_added; + guint n_files_changed; + gchar* last_file_changed; + guint n_files_removed; + gchar* last_file_removed; +} Test; + +static void +file_added (EggFileTracker *tracker, + const gchar *path, + gpointer user_data) +{ + Test *test = user_data; + + g_assert ("should be a non-null path" && path != NULL); + + ++test->n_files_added; + g_free (test->last_file_added); + test->last_file_added = g_strdup (path); +} + +static void +file_changed (EggFileTracker *tracker, + const gchar *path, + gpointer user_data) +{ + Test *test = user_data; + + g_assert ("should be a non-null path" && path != NULL); + + ++test->n_files_changed; + g_free (test->last_file_changed); + test->last_file_changed = g_strdup (path); +} + +static void +file_removed (EggFileTracker *tracker, + const gchar *path, + gpointer user_data) +{ + Test *test = user_data; + + g_assert ("should be a non-null path" && path != NULL); + + ++test->n_files_removed; + g_free (test->last_file_removed); + test->last_file_removed = g_strdup (path); +} + +static void +file_reset_stats (Test *test) +{ + g_free (test->last_file_removed); + g_free (test->last_file_added); + g_free (test->last_file_changed); + test->last_file_removed = test->last_file_added = test->last_file_changed = NULL; + test->n_files_added = test->n_files_changed = test->n_files_removed = 0; +} + +static void +setup (Test *test, gconstpointer unused) +{ + /* Make a test directory */ + test->test_dir = g_build_filename ("/tmp", SUBDIR, NULL); + + test->the_tracker = egg_file_tracker_new (test->test_dir, WILDCARD, NULL); + g_signal_connect (test->the_tracker, "file-added", G_CALLBACK (file_added), test); + g_signal_connect (test->the_tracker, "file-removed", G_CALLBACK (file_removed), test); + g_signal_connect (test->the_tracker, "file-changed", G_CALLBACK (file_changed), test); + + /* Mtime must change so wait between tests */ + sleep (1); + + test->test_file = g_build_filename (test->test_dir, "my-file.woof", NULL); + g_unlink (test->test_file); +} + +static void +teardown (Test *test, gconstpointer unused) +{ + file_reset_stats (test); + g_object_unref (test->the_tracker); + g_free (test->test_dir); + g_free (test->test_file); +} + +static void +test_file_watch (Test *test, gconstpointer unused) +{ + /* A watch for an non-existant directory, should have no responses */ + egg_file_tracker_refresh (test->the_tracker, FALSE); + + g_assert_cmpint (0, ==, test->n_files_added); + g_assert_cmpint (0, ==, test->n_files_changed); + g_assert_cmpint (0, ==, test->n_files_removed); + + g_mkdir_with_parents (test->test_dir, 0700); + + /* Should still have no responses even though it exists */ + egg_file_tracker_refresh (test->the_tracker, FALSE); + + g_assert_cmpint (0, ==, test->n_files_added); + g_assert_cmpint (0, ==, test->n_files_changed); + g_assert_cmpint (0, ==, test->n_files_removed); +} + +static void +test_watch_file (Test *test, gconstpointer unused) +{ + gboolean ret; + + /* Make sure things are clean */ + egg_file_tracker_refresh (test->the_tracker, FALSE); + + test->n_files_added = test->n_files_changed = test->n_files_removed = 0; + test->last_file_added = test->last_file_changed = test->last_file_removed = 0; + + ret = g_file_set_contents (test->test_file, DATA, strlen (DATA), NULL); + g_assert (ret); + + /* Now make sure that file is located */ + egg_file_tracker_refresh (test->the_tracker, FALSE); + + g_assert_cmpint (1, ==, test->n_files_added); + g_assert_cmpint (0, ==, test->n_files_changed); + g_assert_cmpint (0, ==, test->n_files_removed); + + /* The added one should match our file */ + g_assert_cmpstr (test->last_file_added, ==, test->test_file); + + file_reset_stats (test); + sleep (1); + + /* Shouldn't find the file again */ + egg_file_tracker_refresh (test->the_tracker, FALSE); + g_assert_cmpint (0, ==, test->n_files_added); + g_assert_cmpint (0, ==, test->n_files_changed); + g_assert_cmpint (0, ==, test->n_files_removed); + + /* But we should find the file if forced to */ + egg_file_tracker_refresh (test->the_tracker, TRUE); + g_assert_cmpint (0, ==, test->n_files_added); + g_assert_cmpint (1, ==, test->n_files_changed); + g_assert_cmpint (0, ==, test->n_files_removed); + g_assert_cmpstr (test->last_file_changed, ==, test->test_file); + + file_reset_stats (test); + + ret = g_file_set_contents (test->test_file, DATA, strlen (DATA), NULL); + g_assert (ret); + + /* File was updated */ + egg_file_tracker_refresh (test->the_tracker, FALSE); + g_assert_cmpint (0, ==, test->n_files_added); + g_assert_cmpint (1, ==, test->n_files_changed); + g_assert_cmpint (0, ==, test->n_files_removed); + g_assert_cmpstr (test->last_file_changed, ==, test->test_file); + + file_reset_stats (test); + g_unlink (test->test_file); + + /* Now file should be removed */ + egg_file_tracker_refresh (test->the_tracker, FALSE); + + g_assert_cmpint (0, ==, test->n_files_added); + g_assert_cmpint (0, ==, test->n_files_changed); + g_assert_cmpint (1, ==, test->n_files_removed); + g_assert_cmpstr (test->last_file_removed, ==, test->test_file); +} + +static void +test_nomatch (Test *test, gconstpointer unused) +{ + gchar *file = g_build_filename (test->test_dir, "my-file.toot", NULL); + gboolean ret; + + /* Mtime must change so wait between tests */ + sleep (1); + + ret = g_file_set_contents (file, DATA, strlen (DATA), NULL); + g_assert (ret); + + file_reset_stats (test); + + /* Now make sure that file is not located */ + egg_file_tracker_refresh (test->the_tracker, FALSE); + + g_assert_cmpint (0, ==, test->n_files_added); + g_assert_cmpint (0, ==, test->n_files_changed); + g_assert_cmpint (0, ==, test->n_files_removed); + + g_unlink (file); + g_free (file); +} + +int +main (int argc, char **argv) +{ +#if !GLIB_CHECK_VERSION(2,35,0) + g_type_init (); +#endif + g_test_init (&argc, &argv, NULL); + + g_test_add ("/egg/file-tracker/file_watch", Test, NULL, setup, test_file_watch, teardown); + g_test_add ("/egg/file-tracker/watch_file", Test, NULL, setup, test_watch_file, teardown); + g_test_add ("/egg/file-tracker/nomatch", Test, NULL, setup, test_nomatch, teardown); + + return g_test_run (); +} |