/* Virtual File System garbage collection code Copyright (C) 2003-2024 Free Software Foundation, Inc. Written by: Miguel de Icaza, 1995 Jakub Jelinek, 1995 Pavel Machek, 1998 Pavel Roskin, 2003 This file is part of the Midnight Commander. The Midnight Commander 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 3 of the License, or (at your option) any later version. The Midnight Commander 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, see . */ /** * \file * \brief Source: Virtual File System: garbage collection code * \author Miguel de Icaza * \author Jakub Jelinek * \author Pavel Machek * \author Pavel Roskin * \date 1995, 1998, 2003 */ #include #include #include "lib/global.h" #include "lib/event.h" #include "lib/util.h" /* MC_PTR_FREE */ #include "vfs.h" #include "utilvfs.h" #include "gc.h" /* * The garbage collection mechanism is based on "stamps". * * A stamp is a record that says "I'm a filesystem which is no longer in * use. Free me when you get a chance." * * This file contains a set of functions used for managing this stamp. You * should use them when you write your own filesystem. Here are some rules * of thumb: * * (1) When the last open file in your filesystem gets closed, conditionally * create a stamp. You do this with vfs_stamp_create(). (The meaning * of "conditionally" is explained below.) * * (2) When a file in your filesystem is opened, delete the stamp. You do * this with vfs_rmstamp(). * * (3) When a path inside your filesystem is invoked, call vfs_stamp() to * postpone the free'ing of your filesystem a bit. (This simply updates * a timestamp variable inside the stamp.) * * Additionally, when a user navigates to a new directory in a panel (or a * programmer uses mc_chdir()), a stamp is conditionally created for the * previous directory's filesystem. This ensures that that filesystem is * free'ed. (see: _do_panel_cd() -> vfs_release_path(); mc_chdir()). * * We've spoken here of "conditionally creating" a stamp. What we mean is * that vfs_stamp_create() is to be used: this function creates a stamp * only if no directories are open (aka "active") in your filesystem. (If * there _are_ directories open, it means that the filesystem is in use, in * which case we don't want to free it.) */ /*** global variables ****************************************************************************/ int vfs_timeout = 60; /* VFS timeout in seconds */ /*** file scope macro definitions ****************************************************************/ #define VFS_STAMPING(a) ((struct vfs_stamping *)(a)) /*** file scope type declarations ****************************************************************/ struct vfs_stamping { struct vfs_class *v; vfsid id; gint64 time; }; /*** forward declarations (file scope functions) *************************************************/ /*** file scope variables ************************************************************************/ static GSList *stamps = NULL; /* --------------------------------------------------------------------------------------------- */ /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ static gint vfs_stamp_compare (gconstpointer a, gconstpointer b) { const struct vfs_stamping *vsa = (const struct vfs_stamping *) a; const struct vfs_stamping *vsb = (const struct vfs_stamping *) b; return (vsa == NULL || vsb == NULL || (vsa->v == vsb->v && vsa->id == vsb->id)) ? 0 : 1; } /* --------------------------------------------------------------------------------------------- */ static void vfs_addstamp (struct vfs_class *v, vfsid id) { if ((v->flags & VFSF_LOCAL) == 0 && id != NULL && !vfs_stamp (v, id)) { struct vfs_stamping *stamp; stamp = g_new (struct vfs_stamping, 1); stamp->v = v; stamp->id = id; stamp->time = g_get_monotonic_time (); stamps = g_slist_append (stamps, stamp); } } /* --------------------------------------------------------------------------------------------- */ /*** public functions ****************************************************************************/ /* --------------------------------------------------------------------------------------------- */ gboolean vfs_stamp (struct vfs_class *v, vfsid id) { struct vfs_stamping what = { .v = v, .id = id }; GSList *stamp; gboolean ret = FALSE; stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare); if (stamp != NULL && stamp->data != NULL) { VFS_STAMPING (stamp->data)->time = g_get_monotonic_time (); ret = TRUE; } return ret; } /* --------------------------------------------------------------------------------------------- */ void vfs_rmstamp (struct vfs_class *v, vfsid id) { struct vfs_stamping what = { .v = v, .id = id }; GSList *stamp; stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare); if (stamp != NULL) { g_free (stamp->data); stamps = g_slist_delete_link (stamps, stamp); } } /* --------------------------------------------------------------------------------------------- */ void vfs_stamp_path (const vfs_path_t *vpath) { vfsid id; struct vfs_class *me; me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath)); id = vfs_getid (vpath); vfs_addstamp (me, id); } /* --------------------------------------------------------------------------------------------- */ /** * Create a new timestamp item by VFS class and VFS id. */ void vfs_stamp_create (struct vfs_class *vclass, vfsid id) { vfsid nvfsid; ev_vfs_stamp_create_t event_data = { vclass, id, FALSE }; const vfs_path_t *vpath; struct vfs_class *me; /* There are three directories we have to take care of: current_dir, current_panel->cwd and other_panel->cwd. Although most of the time either current_dir and current_panel->cwd or current_dir and other_panel->cwd are the same, it's possible that all three are different -- Norbert */ if (!mc_event_present (MCEVENT_GROUP_CORE, "vfs_timestamp")) return; vpath = vfs_get_raw_current_dir (); me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath)); nvfsid = vfs_getid (vpath); vfs_rmstamp (me, nvfsid); if (!(id == NULL || (me == vclass && nvfsid == id))) { mc_event_raise (MCEVENT_GROUP_CORE, "vfs_timestamp", (gpointer) & event_data); if (!event_data.ret && vclass != NULL && vclass->nothingisopen != NULL && vclass->nothingisopen (id)) vfs_addstamp (vclass, id); } } /* --------------------------------------------------------------------------------------------- */ /** This is called from timeout handler with now = FALSE, or can be called with now = TRUE to force freeing all filesystems */ void vfs_expire (gboolean now) { static gboolean locked = FALSE; gint64 curr_time, exp_time; GSList *stamp; /* Avoid recursive invocation, e.g. when one of the free functions calls message */ if (locked) return; locked = TRUE; curr_time = g_get_monotonic_time (); exp_time = curr_time - vfs_timeout * G_USEC_PER_SEC; if (now) { /* reverse list to free nested VFSes at first */ stamps = g_slist_reverse (stamps); } /* NULLize stamps that point to expired VFS */ for (stamp = stamps; stamp != NULL; stamp = g_slist_next (stamp)) { struct vfs_stamping *stamping = VFS_STAMPING (stamp->data); if (now) { /* free VFS forced */ if (stamping->v->free != NULL) stamping->v->free (stamping->id); MC_PTR_FREE (stamp->data); } else if (stamping->time <= exp_time) { /* update timestamp of VFS that is in use, or free unused VFS */ if (stamping->v->nothingisopen != NULL && !stamping->v->nothingisopen (stamping->id)) stamping->time = curr_time; else { if (stamping->v->free != NULL) stamping->v->free (stamping->id); MC_PTR_FREE (stamp->data); } } } /* then remove NULLized stamps */ stamps = g_slist_remove_all (stamps, NULL); locked = FALSE; } /* --------------------------------------------------------------------------------------------- */ /* * Return the number of seconds remaining to the vfs timeout. * FIXME: The code should be improved to actually return the number of * seconds until the next item times out. */ int vfs_timeouts (void) { return stamps != NULL ? 10 : 0; } /* --------------------------------------------------------------------------------------------- */ void vfs_timeout_handler (void) { vfs_expire (FALSE); } /* --------------------------------------------------------------------------------------------- */ void vfs_release_path (const vfs_path_t *vpath) { vfsid id; struct vfs_class *me; me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath)); id = vfs_getid (vpath); vfs_stamp_create (me, id); } /* --------------------------------------------------------------------------------------------- */ /* Free all data */ void vfs_gc_done (void) { vfs_expire (TRUE); } /* --------------------------------------------------------------------------------------------- */