/* * Copyright (C) 2023 António Fernandes * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "nautilus-fd-holder.h" /** * NautilusFdHolder: * * A helper object which wraps the process of getting, keeping, and releasing * file descriptor references. * * It can be reused for multiple locations, but only one at a time. * * The original use case is for #NautilusWindowSlot to keep autofs locations * from getting unmounted while we are displaying them. * * For convenience, we use GFileEnumerator as the real file descriptor holder. * This relies on the assumption that its implementation for local files calls * `opendir()` on creation and `closedir()` on destruction. */ struct _NautilusFdHolder { GObject parent_instance; GFile *location; GFileEnumerator *enumerator; GCancellable *enumerator_cancellable; }; G_DEFINE_FINAL_TYPE (NautilusFdHolder, nautilus_fd_holder, G_TYPE_OBJECT) enum { PROP_0, PROP_LOCATION, N_PROPS }; static GParamSpec *properties[N_PROPS]; static GHashTable *location_enumerator_map = NULL; static inline void ensure_location_enumerator_map (void) { if (G_UNLIKELY (location_enumerator_map == NULL)) { location_enumerator_map = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); } } static void on_enumerator_destroyed (gpointer data, GObject *where_the_object_was) { GFile *location = G_FILE (data); if (location_enumerator_map != NULL && where_the_object_was == g_hash_table_lookup (location_enumerator_map, location)) { g_hash_table_remove (location_enumerator_map, location); } } static void on_enumerator_ready (GObject *source_object, GAsyncResult *res, gpointer data) { GFile *location = G_FILE (source_object); g_autoptr (GError) error = NULL; g_autoptr (GFileEnumerator) enumerator = g_file_enumerate_children_finish (location, res, &error); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { return; } NautilusFdHolder *self = NAUTILUS_FD_HOLDER (data); g_return_if_fail (g_file_equal (location, self->location)); g_warn_if_fail (self->enumerator == NULL); ensure_location_enumerator_map (); GFileEnumerator *existing = g_hash_table_lookup (location_enumerator_map, location); if (G_LIKELY (existing == NULL)) { g_hash_table_insert (location_enumerator_map, g_object_ref (location), enumerator); g_object_weak_ref (G_OBJECT (enumerator), on_enumerator_destroyed, location); } else { /* Cope with the rare case of `g_file_enumerate_children_async() being * called twice for the same location before the first callback by using * the first one instead. */ g_set_object (&enumerator, existing); } g_set_object (&self->enumerator, enumerator); } static void update_fd_holder (NautilusFdHolder *self) { g_cancellable_cancel (self->enumerator_cancellable); g_clear_object (&self->enumerator_cancellable); g_clear_object (&self->enumerator); if (self->location == NULL || !g_file_is_native (self->location)) { return; } if (G_LIKELY (location_enumerator_map != NULL)) { GFileEnumerator *existing = g_hash_table_lookup (location_enumerator_map, self->location); if (existing != NULL) { self->enumerator = g_object_ref (existing); return; } } self->enumerator_cancellable = g_cancellable_new (); g_file_enumerate_children_async (self->location, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, G_PRIORITY_LOW, self->enumerator_cancellable, on_enumerator_ready, self); } NautilusFdHolder * nautilus_fd_holder_new (void) { return g_object_new (NAUTILUS_TYPE_FD_HOLDER, NULL); } static void nautilus_fd_holder_finalize (GObject *object) { NautilusFdHolder *self = (NautilusFdHolder *) object; g_cancellable_cancel (self->enumerator_cancellable); g_clear_object (&self->enumerator_cancellable); g_clear_object (&self->enumerator); if (location_enumerator_map != NULL && g_hash_table_size (location_enumerator_map) == 0) { g_clear_pointer (&location_enumerator_map, g_hash_table_destroy); } g_clear_object (&self->location); G_OBJECT_CLASS (nautilus_fd_holder_parent_class)->finalize (object); } static void nautilus_fd_holder_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NautilusFdHolder *self = NAUTILUS_FD_HOLDER (object); switch (prop_id) { case PROP_LOCATION: { g_value_set_object (value, self->location); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } } static void nautilus_fd_holder_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NautilusFdHolder *self = NAUTILUS_FD_HOLDER (object); switch (prop_id) { case PROP_LOCATION: { if (g_set_object (&self->location, g_value_get_object (value))) { update_fd_holder (self); g_object_notify_by_pspec (object, properties[PROP_LOCATION]); } } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } } static void nautilus_fd_holder_class_init (NautilusFdHolderClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = nautilus_fd_holder_finalize; object_class->get_property = nautilus_fd_holder_get_property; object_class->set_property = nautilus_fd_holder_set_property; properties[PROP_LOCATION] = g_param_spec_object ("location", NULL, NULL, G_TYPE_FILE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, properties); } static void nautilus_fd_holder_init (NautilusFdHolder *self) { } void nautilus_fd_holder_set_location (NautilusFdHolder *self, GFile *location) { g_return_if_fail (NAUTILUS_IS_FD_HOLDER (self)); g_return_if_fail (location == NULL || G_IS_FILE (location)); g_object_set (self, "location", location, NULL); } static gboolean close_if_within_mount (GFile *mount_root, GFile *location, GFileEnumerator *enumerator) { g_return_val_if_fail (G_IS_FILE (location), FALSE); if (g_file_equal (location, mount_root) || g_file_has_prefix (location, mount_root)) { g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), TRUE); g_file_enumerator_close (enumerator, NULL, NULL); return TRUE; } else { return FALSE; } } void nautilus_fd_holders_release_for_mount (GMount *mount) { g_return_if_fail (G_IS_MOUNT (mount)); g_autoptr (GFile) mount_root = g_mount_get_root (mount); GHashTableIter iter; GFile *location; GFileEnumerator *enumerator; if (G_UNLIKELY (location_enumerator_map == NULL)) { return; } g_hash_table_iter_init (&iter, location_enumerator_map); while (g_hash_table_iter_next (&iter, (gpointer *) &location, (gpointer *) &enumerator)) { if (close_if_within_mount (mount_root, location, enumerator)) { g_hash_table_iter_remove (&iter); } } }