From 9761517998b59d684f03a14911de9fb3c8d67c76 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Thu, 11 Feb 2021 16:04:29 +0100 Subject: egg: Port egg-file-tracker from gnome-keyring Although this is not a fully fledged file monitor as GFileMonitor, it serves better in our use-case: preloading public keys in ssh-agent. --- egg/egg-file-tracker.c | 303 +++++++++++++++++++++++++++++++++++++++++++++++++ egg/egg-file-tracker.h | 59 ++++++++++ egg/meson.build | 1 + 3 files changed, 363 insertions(+) create mode 100644 egg/egg-file-tracker.c create mode 100644 egg/egg-file-tracker.h (limited to 'egg') diff --git a/egg/egg-file-tracker.c b/egg/egg-file-tracker.c new file mode 100644 index 0000000..0dbfbef --- /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, + . + + Author: Stef Walter +*/ + +#include "config.h" + +#include "egg-file-tracker.h" + +#include "egg/egg-error.h" + +#include +#include + +#include +#include +#include + +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 0000000..9ce2f9b --- /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, + . + + Author: Stef Walter +*/ + +#ifndef __EGG_FILE_TRACKER_H__ +#define __EGG_FILE_TRACKER_H__ + +#include + +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/meson.build b/egg/meson.build index f0dcab3..6888a5c 100644 --- a/egg/meson.build +++ b/egg/meson.build @@ -6,6 +6,7 @@ libegg_sources = [ 'egg-dh.c', 'egg-dn.c', 'egg-decimal.c', + 'egg-file-tracker.c', 'egg-hex.c', 'egg-hkdf.c', 'egg-libgcrypt.c', -- cgit v1.2.1