1 /*
2 Virtual File System garbage collection code
3
4 Copyright (C) 2003-2025
5 Free Software Foundation, Inc.
6
7 Written by:
8 Miguel de Icaza, 1995
9 Jakub Jelinek, 1995
10 Pavel Machek, 1998
11 Pavel Roskin, 2003
12
13 This file is part of the Midnight Commander.
14
15 The Midnight Commander is free software: you can redistribute it
16 and/or modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation, either version 3 of the License,
18 or (at your option) any later version.
19
20 The Midnight Commander is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <https://www.gnu.org/licenses/>.
27 */
28
29 /**
30 * \file
31 * \brief Source: Virtual File System: garbage collection code
32 * \author Miguel de Icaza
33 * \author Jakub Jelinek
34 * \author Pavel Machek
35 * \author Pavel Roskin
36 * \date 1995, 1998, 2003
37 */
38
39 #include <config.h>
40
41 #include <stdlib.h>
42
43 #include "lib/global.h"
44 #include "lib/event.h"
45 #include "lib/util.h" // MC_PTR_FREE
46
47 #include "vfs.h"
48 #include "utilvfs.h"
49
50 #include "gc.h"
51
52 /*
53 * The garbage collection mechanism is based on "stamps".
54 *
55 * A stamp is a record that says "I'm a filesystem which is no longer in
56 * use. Free me when you get a chance."
57 *
58 * This file contains a set of functions used for managing this stamp. You
59 * should use them when you write your own filesystem. Here are some rules
60 * of thumb:
61 *
62 * (1) When the last open file in your filesystem gets closed, conditionally
63 * create a stamp. You do this with vfs_stamp_create(). (The meaning
64 * of "conditionally" is explained below.)
65 *
66 * (2) When a file in your filesystem is opened, delete the stamp. You do
67 * this with vfs_rmstamp().
68 *
69 * (3) When a path inside your filesystem is invoked, call vfs_stamp() to
70 * postpone the free'ing of your filesystem a bit. (This simply updates
71 * a timestamp variable inside the stamp.)
72 *
73 * Additionally, when a user navigates to a new directory in a panel (or a
74 * programmer uses mc_chdir()), a stamp is conditionally created for the
75 * previous directory's filesystem. This ensures that that filesystem is
76 * free'ed. (see: _do_panel_cd() -> vfs_release_path(); mc_chdir()).
77 *
78 * We've spoken here of "conditionally creating" a stamp. What we mean is
79 * that vfs_stamp_create() is to be used: this function creates a stamp
80 * only if no directories are open (aka "active") in your filesystem. (If
81 * there _are_ directories open, it means that the filesystem is in use, in
82 * which case we don't want to free it.)
83 */
84
85 /*** global variables ****************************************************************************/
86
87 int vfs_timeout = 60; // VFS timeout in seconds
88
89 /*** file scope macro definitions ****************************************************************/
90
91 #define VFS_STAMPING(a) ((struct vfs_stamping *) (a))
92
93 /*** file scope type declarations ****************************************************************/
94
95 struct vfs_stamping
96 {
97 struct vfs_class *v;
98 vfsid id;
99 gint64 time;
100 };
101
102 /*** forward declarations (file scope functions) *************************************************/
103
104 /*** file scope variables ************************************************************************/
105
106 static GSList *stamps = NULL;
107
108 /* --------------------------------------------------------------------------------------------- */
109 /*** file scope functions ************************************************************************/
110 /* --------------------------------------------------------------------------------------------- */
111
112 static gint
113 vfs_stamp_compare (gconstpointer a, gconstpointer b)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
114 {
115 const struct vfs_stamping *vsa = (const struct vfs_stamping *) a;
116 const struct vfs_stamping *vsb = (const struct vfs_stamping *) b;
117
118 return (vsa == NULL || vsb == NULL || (vsa->v == vsb->v && vsa->id == vsb->id)) ? 0 : 1;
119 }
120
121 /* --------------------------------------------------------------------------------------------- */
122
123 static void
124 vfs_addstamp (struct vfs_class *v, vfsid id)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
125 {
126 if ((v->flags & VFSF_LOCAL) == 0 && id != NULL && !vfs_stamp (v, id))
127 {
128 struct vfs_stamping *stamp;
129
130 stamp = g_new (struct vfs_stamping, 1);
131 stamp->v = v;
132 stamp->id = id;
133 stamp->time = g_get_monotonic_time ();
134
135 stamps = g_slist_append (stamps, stamp);
136 }
137 }
138
139 /* --------------------------------------------------------------------------------------------- */
140 /*** public functions ****************************************************************************/
141 /* --------------------------------------------------------------------------------------------- */
142
143 gboolean
144 vfs_stamp (struct vfs_class *v, vfsid id)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
145 {
146 struct vfs_stamping what = {
147 .v = v,
148 .id = id,
149 };
150 GSList *stamp;
151 gboolean ret = FALSE;
152
153 stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare);
154 if (stamp != NULL && stamp->data != NULL)
155 {
156 VFS_STAMPING (stamp->data)->time = g_get_monotonic_time ();
157 ret = TRUE;
158 }
159
160 return ret;
161 }
162
163 /* --------------------------------------------------------------------------------------------- */
164
165 void
166 vfs_rmstamp (struct vfs_class *v, vfsid id)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
167 {
168 struct vfs_stamping what = {
169 .v = v,
170 .id = id,
171 };
172 GSList *stamp;
173
174 stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare);
175 if (stamp != NULL)
176 {
177 g_free (stamp->data);
178 stamps = g_slist_delete_link (stamps, stamp);
179 }
180 }
181
182 /* --------------------------------------------------------------------------------------------- */
183
184 void
185 vfs_stamp_path (const vfs_path_t *vpath)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
186 {
187 vfsid id;
188 struct vfs_class *me;
189
190 me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
191 id = vfs_getid (vpath);
192 vfs_addstamp (me, id);
193 }
194
195 /* --------------------------------------------------------------------------------------------- */
196 /**
197 * Create a new timestamp item by VFS class and VFS id.
198 */
199
200 void
201 vfs_stamp_create (struct vfs_class *vclass, vfsid id)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
202 {
203 vfsid nvfsid;
204
205 ev_vfs_stamp_create_t event_data = { vclass, id, FALSE };
206 const vfs_path_t *vpath;
207 struct vfs_class *me;
208
209 /* There are three directories we have to take care of: current_dir,
210 current_panel->cwd and other_panel->cwd. Although most of the time either
211 current_dir and current_panel->cwd or current_dir and other_panel->cwd are the
212 same, it's possible that all three are different -- Norbert */
213
214 if (!mc_event_present (MCEVENT_GROUP_CORE, "vfs_timestamp"))
215 return;
216
217 vpath = vfs_get_raw_current_dir ();
218 me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
219
220 nvfsid = vfs_getid (vpath);
221 vfs_rmstamp (me, nvfsid);
222
223 if (!(id == NULL || (me == vclass && nvfsid == id)))
224 {
225 mc_event_raise (MCEVENT_GROUP_CORE, "vfs_timestamp", (gpointer) &event_data);
226
227 if (!event_data.ret && vclass != NULL && vclass->nothingisopen != NULL
228 && vclass->nothingisopen (id))
229 vfs_addstamp (vclass, id);
230 }
231 }
232
233 /* --------------------------------------------------------------------------------------------- */
234 /** This is called from timeout handler with now = FALSE,
235 or can be called with now = TRUE to force freeing all filesystems */
236
237 void
238 vfs_expire (gboolean now)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
239 {
240 static gboolean locked = FALSE;
241 gint64 curr_time, exp_time;
242 GSList *stamp;
243
244 /* Avoid recursive invocation, e.g. when one of the free functions
245 calls message */
246 if (locked)
247 return;
248 locked = TRUE;
249
250 curr_time = g_get_monotonic_time ();
251 exp_time = curr_time - vfs_timeout * G_USEC_PER_SEC;
252
253 if (now)
254 {
255 // reverse list to free nested VFSes at first
256 stamps = g_slist_reverse (stamps);
257 }
258
259 // NULLize stamps that point to expired VFS
260 for (stamp = stamps; stamp != NULL; stamp = g_slist_next (stamp))
261 {
262 struct vfs_stamping *stamping = VFS_STAMPING (stamp->data);
263
264 if (now)
265 {
266 // free VFS forced
267 if (stamping->v->free != NULL)
268 stamping->v->free (stamping->id);
269 MC_PTR_FREE (stamp->data);
270 }
271 else if (stamping->time <= exp_time)
272 {
273 // update timestamp of VFS that is in use, or free unused VFS
274 if (stamping->v->nothingisopen != NULL && !stamping->v->nothingisopen (stamping->id))
275 stamping->time = curr_time;
276 else
277 {
278 if (stamping->v->free != NULL)
279 stamping->v->free (stamping->id);
280 MC_PTR_FREE (stamp->data);
281 }
282 }
283 }
284
285 // then remove NULLized stamps
286 stamps = g_slist_remove_all (stamps, NULL);
287
288 locked = FALSE;
289 }
290
291 /* --------------------------------------------------------------------------------------------- */
292 /*
293 * Return the number of seconds remaining to the vfs timeout.
294 * FIXME: The code should be improved to actually return the number of
295 * seconds until the next item times out.
296 */
297
298 int
299 vfs_timeouts (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
300 {
301 return stamps != NULL ? 10 : 0;
302 }
303
304 /* --------------------------------------------------------------------------------------------- */
305
306 void
307 vfs_timeout_handler (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
308 {
309 vfs_expire (FALSE);
310 }
311
312 /* --------------------------------------------------------------------------------------------- */
313
314 void
315 vfs_release_path (const vfs_path_t *vpath)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
316 {
317 vfsid id;
318 struct vfs_class *me;
319
320 me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
321 id = vfs_getid (vpath);
322 vfs_stamp_create (me, id);
323 }
324
325 /* --------------------------------------------------------------------------------------------- */
326 /* Free all data */
327
328 void
329 vfs_gc_done (void)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
330 {
331 vfs_expire (TRUE);
332 }
333
334 /* --------------------------------------------------------------------------------------------- */