diff options
author | Corentin Noël <corentin.noel@collabora.com> | 2022-08-08 09:32:35 +0200 |
---|---|---|
committer | Corentin Noël <corentin.noel@collabora.com> | 2022-08-08 09:43:07 +0200 |
commit | 2578574a9b5af82753f9416684fbd86b0b5244fd (patch) | |
tree | 6c2d76571242a06485feabd404d3600fd6d7eb4d | |
parent | 46d67a18ba3e64297f95f7a900eeb9da24afd452 (diff) | |
download | gcr-tintou/no-file-tracker.tar.gz |
ssh-agent-preload: Remove usage of EggFileTrackertintou/no-file-tracker
We can have the same features with a GFileMonitor and a GFileEnumerator.
-rw-r--r-- | egg/egg-file-tracker.c | 303 | ||||
-rw-r--r-- | egg/egg-file-tracker.h | 59 | ||||
-rw-r--r-- | egg/meson.build | 1 | ||||
-rw-r--r-- | gcr/gcr-ssh-agent-preload.c | 172 | ||||
-rw-r--r-- | gcr/gcr-ssh-agent-preload.h | 6 |
5 files changed, 123 insertions, 418 deletions
diff --git a/egg/egg-file-tracker.c b/egg/egg-file-tracker.c deleted file mode 100644 index 0dbfbef..0000000 --- a/egg/egg-file-tracker.c +++ /dev/null @@ -1,303 +0,0 @@ -/* -*- 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 deleted file mode 100644 index 9ce2f9b..0000000 --- a/egg/egg-file-tracker.h +++ /dev/null @@ -1,59 +0,0 @@ -/* -*- 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/meson.build b/egg/meson.build index b577fd3..d475b0e 100644 --- a/egg/meson.build +++ b/egg/meson.build @@ -6,7 +6,6 @@ libegg_sources = [ 'egg-dh.c', 'egg-dn.c', 'egg-decimal.c', - 'egg-file-tracker.c', 'egg-hex.c', 'egg-hkdf.c', 'egg-libgcrypt.c', diff --git a/gcr/gcr-ssh-agent-preload.c b/gcr/gcr-ssh-agent-preload.c index 4e5bc66..ef4e0d8 100644 --- a/gcr/gcr-ssh-agent-preload.c +++ b/gcr/gcr-ssh-agent-preload.c @@ -26,7 +26,6 @@ #include "gcr-ssh-agent-preload.h" #include "gcr-ssh-agent-util.h" -#include "egg/egg-file-tracker.h" #include <string.h> enum { @@ -41,11 +40,12 @@ struct _GcrSshAgentPreload gchar *path; GHashTable *keys_by_public_filename; GHashTable *keys_by_public_key; - EggFileTracker *file_tracker; + GFileMonitor *file_monitor; GMutex lock; }; G_DEFINE_TYPE (GcrSshAgentPreload, gcr_ssh_agent_preload, G_TYPE_OBJECT); +G_DEFINE_BOXED_TYPE(GcrSshAgentKeyInfo, gcr_ssh_agent_key_info, gcr_ssh_agent_key_info_copy, gcr_ssh_agent_key_info_free) void gcr_ssh_agent_key_info_free (gpointer boxed) @@ -53,9 +53,9 @@ gcr_ssh_agent_key_info_free (gpointer boxed) GcrSshAgentKeyInfo *info = boxed; if (!info) return; - g_bytes_unref (info->public_key); - g_free (info->comment); - g_free (info->filename); + g_clear_pointer (&info->public_key, g_bytes_unref); + g_clear_pointer (&info->comment, g_free); + g_clear_pointer (&info->filename, g_free); g_free (info); } @@ -70,12 +70,11 @@ gcr_ssh_agent_key_info_copy (gpointer boxed) return copy; } -static void file_load_inlock (EggFileTracker *tracker, - const gchar *path, - gpointer user_data); -static void file_remove_inlock (EggFileTracker *tracker, - const gchar *path, - gpointer user_data); +static void changed_inlock (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data); static void gcr_ssh_agent_preload_init (GcrSshAgentPreload *self) @@ -88,11 +87,17 @@ static void gcr_ssh_agent_preload_constructed (GObject *object) { GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (object); + GError *error = NULL; + GFile *file; - self->file_tracker = egg_file_tracker_new (self->path, "*.pub", NULL); - g_signal_connect (self->file_tracker, "file-added", G_CALLBACK (file_load_inlock), self); - g_signal_connect (self->file_tracker, "file-removed", G_CALLBACK (file_remove_inlock), self); - g_signal_connect (self->file_tracker, "file-changed", G_CALLBACK (file_load_inlock), self); + file = g_file_new_for_path (self->path); + self->file_monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, &error); + if (!self->file_monitor) { + g_critical ("Unable to listen to directory %s: %s", self->path, error->message); + g_clear_error (&error); + } else + g_signal_connect (self->file_monitor, "changed", G_CALLBACK (changed_inlock), self); + g_object_unref (file); G_OBJECT_CLASS (gcr_ssh_agent_preload_parent_class)->constructed (object); } @@ -120,10 +125,11 @@ gcr_ssh_agent_preload_finalize (GObject *object) { GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (object); - g_free (self->path); + g_clear_pointer (&self->path, g_free); g_clear_pointer (&self->keys_by_public_key, g_hash_table_unref); g_clear_pointer (&self->keys_by_public_filename, g_hash_table_unref); - g_clear_object (&self->file_tracker); + g_signal_handlers_disconnect_by_func (self->file_monitor, changed_inlock, self); + g_clear_object (&self->file_monitor); g_mutex_clear (&self->lock); @@ -143,15 +149,6 @@ gcr_ssh_agent_preload_class_init (GcrSshAgentPreloadClass *klass) G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); } -static gchar * -private_path_for_public (const gchar *public_path) -{ - if (g_str_has_suffix (public_path, ".pub")) - return g_strndup (public_path, strlen (public_path) - 4); - - return NULL; -} - static GBytes * file_get_contents (const gchar *path, gboolean must_be_present) @@ -171,11 +168,8 @@ file_get_contents (const gchar *path, } static void -file_remove_inlock (EggFileTracker *tracker, - const gchar *path, - gpointer user_data) +file_remove_inlock (GcrSshAgentPreload *self, const gchar *path) { - GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (user_data); GcrSshAgentKeyInfo *info; info = g_hash_table_lookup (self->keys_by_public_filename, path); @@ -186,38 +180,40 @@ file_remove_inlock (EggFileTracker *tracker, } static void -file_load_inlock (EggFileTracker *tracker, - const gchar *path, - gpointer user_data) +file_load_inlock (GcrSshAgentPreload *self, const gchar *path) { - GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (user_data); - gchar *private_path; - GBytes *private_bytes; + gchar *private_path = NULL; GBytes *public_bytes; - GBytes *public_key; - GcrSshAgentKeyInfo *info; - gchar *comment; - file_remove_inlock (tracker, path, user_data); + file_remove_inlock (self, path); - private_path = private_path_for_public (path); + if (g_str_has_suffix (path, ".pub")) { + GBytes *private_bytes; - private_bytes = file_get_contents (private_path, FALSE); - if (!private_bytes) { - g_debug ("no private key present for public key: %s", path); - g_free (private_path); - return; + private_path = g_strndup (path, strlen (path) - 4); + private_bytes = file_get_contents (private_path, FALSE); + if (!private_bytes) { + g_debug ("no private key present for public key: %s", path); + g_free (private_path); + return; + } + + g_bytes_unref (private_bytes); } public_bytes = file_get_contents (path, TRUE); if (public_bytes) { + gchar *comment = NULL; + GBytes *public_key; + public_key = _gcr_ssh_agent_parse_public_key (public_bytes, &comment); if (public_key) { + GcrSshAgentKeyInfo *info; + info = g_new0 (GcrSshAgentKeyInfo, 1); - info->filename = private_path; - private_path = NULL; - info->public_key = public_key; - info->comment = comment; + info->filename = g_steal_pointer (&private_path); + info->public_key = g_steal_pointer (&public_key); + info->comment = g_steal_pointer (&comment); g_hash_table_replace (self->keys_by_public_filename, g_strdup (path), info); g_hash_table_replace (self->keys_by_public_key, info->public_key, info); } else { @@ -227,10 +223,80 @@ file_load_inlock (EggFileTracker *tracker, g_bytes_unref (public_bytes); } - g_bytes_unref (private_bytes); g_free (private_path); } +static void +changed_inlock (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (user_data); + char *path; + + switch (event_type) { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + path = g_file_get_path (file); + if (g_str_has_suffix (path, ".pub")) + file_load_inlock (self, path); + g_free (path); + break; + case G_FILE_MONITOR_EVENT_DELETED: + path = g_file_get_path (file); + if (g_str_has_suffix (path, ".pub")) + file_remove_inlock (self, path); + g_free (path); + break; + default: + break; + } +} + +static void +refresh_listened_directory (GcrSshAgentPreload *self) +{ + GFile *file; + GFileEnumerator *direnum; + GError *error = NULL; + + file = g_file_new_for_path (self->path); + direnum = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE G_FILE_ATTRIBUTE_STANDARD_NAME G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + g_object_unref (file); + + while (TRUE) { + GFileInfo *info; + const char *name; + if (!g_file_enumerator_iterate (direnum, &info, NULL, NULL, &error)) { + g_critical ("Error while iterating files in %s: %s", self->path, error->message); + g_clear_error (&error); + break; + } + + if (!info) + break; + + if (g_file_info_get_is_hidden (info) || + g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + continue; + + name = g_file_info_get_name (info); + if (g_str_has_suffix (name, ".pub")) { + char *path = g_build_filename (self->path, name, NULL); + file_load_inlock (self, path); + g_free (path); + } + } + + g_object_unref (direnum); +} + GcrSshAgentPreload * gcr_ssh_agent_preload_new (const gchar *path) { @@ -248,7 +314,7 @@ gcr_ssh_agent_preload_get_keys (GcrSshAgentPreload *self) g_mutex_lock (&self->lock); - egg_file_tracker_refresh (self->file_tracker, FALSE); + refresh_listened_directory (self); g_hash_table_iter_init (&iter, self->keys_by_public_key); while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&info)) @@ -267,7 +333,7 @@ gcr_ssh_agent_preload_lookup_by_public_key (GcrSshAgentPreload *self, g_mutex_lock (&self->lock); - egg_file_tracker_refresh (self->file_tracker, FALSE); + refresh_listened_directory (self); info = g_hash_table_lookup (self->keys_by_public_key, public_key); if (info) diff --git a/gcr/gcr-ssh-agent-preload.h b/gcr/gcr-ssh-agent-preload.h index 1ea5258..98de032 100644 --- a/gcr/gcr-ssh-agent-preload.h +++ b/gcr/gcr-ssh-agent-preload.h @@ -32,8 +32,10 @@ typedef struct { gchar *comment; } GcrSshAgentKeyInfo; -void gcr_ssh_agent_key_info_free (gpointer boxed); -gpointer gcr_ssh_agent_key_info_copy (gpointer boxed); +#define GCR_TYPE_SSH_AGENT_KEY_INFO gcr_ssh_agent_key_info_get_type () +GType gcr_ssh_agent_key_info_get_type (void) G_GNUC_CONST; +void gcr_ssh_agent_key_info_free (gpointer boxed); +gpointer gcr_ssh_agent_key_info_copy (gpointer boxed); #define GCR_TYPE_SSH_AGENT_PRELOAD gcr_ssh_agent_preload_get_type () G_DECLARE_FINAL_TYPE (GcrSshAgentPreload, gcr_ssh_agent_preload, GCR, SSH_AGENT_PRELOAD, GObject) |