1 /*
2    Virtual File System garbage collection code
3 
4    Copyright (C) 2003-2021
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 <http://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 
40 #include <config.h>
41 
42 #include <stdlib.h>
43 
44 #include "lib/global.h"
45 #include "lib/event.h"
46 #include "lib/util.h"           /* MC_PTR_FREE */
47 
48 #include "vfs.h"
49 #include "utilvfs.h"
50 
51 #include "gc.h"
52 
53 /*
54  * The garbage collection mechanism is based on "stamps".
55  *
56  * A stamp is a record that says "I'm a filesystem which is no longer in
57  * use. Free me when you get a chance."
58  *
59  * This file contains a set of functions used for managing this stamp. You
60  * should use them when you write your own filesystem. Here are some rules
61  * of thumb:
62  *
63  * (1) When the last open file in your filesystem gets closed, conditionally
64  *     create a stamp. You do this with vfs_stamp_create(). (The meaning
65  *     of "conditionaly" is explained below.)
66  *
67  * (2) When a file in your filesystem is opened, delete the stamp. You do
68  *     this with vfs_rmstamp().
69  *
70  * (3) When a path inside your filesystem is invoked, call vfs_stamp() to
71  *     postpone the free'ing of your filesystem a bit. (This simply updates
72  *     a timestamp variable inside the stamp.)
73  *
74  * Additionally, when a user navigates to a new directory in a panel (or a
75  * programmer uses mc_chdir()), a stamp is conditionally created for the
76  * previous directory's filesystem. This ensures that that filesystem is
77  * free'ed. (see: _do_panel_cd() -> vfs_release_path(); mc_chdir()).
78  *
79  * We've spoken here of "conditionally creating" a stamp. What we mean is
80  * that vfs_stamp_create() is to be used: this function creates a stamp
81  * only if no directories are open (aka "active") in your filesystem. (If
82  * there _are_ directories open, it means that the filesystem is in use, in
83  * which case we don't want to free it.)
84  */
85 
86 /*** global variables ****************************************************************************/
87 
88 int vfs_timeout = 60;           /* VFS timeout in seconds */
89 
90 /*** file scope macro definitions ****************************************************************/
91 
92 #define VFS_STAMPING(a) ((struct vfs_stamping *)(a))
93 
94 /*** file scope type declarations ****************************************************************/
95 
96 struct vfs_stamping
97 {
98     struct vfs_class *v;
99     vfsid id;
100     gint64 time;
101 };
102 
103 /*** file scope variables ************************************************************************/
104 
105 static GSList *stamps = NULL;
106 
107 /* --------------------------------------------------------------------------------------------- */
108 /*** file scope functions ************************************************************************/
109 /* --------------------------------------------------------------------------------------------- */
110 
111 static gint
vfs_stamp_compare(gconstpointer a,gconstpointer b)112 vfs_stamp_compare (gconstpointer a, gconstpointer b)
113 {
114     const struct vfs_stamping *vsa = (const struct vfs_stamping *) a;
115     const struct vfs_stamping *vsb = (const struct vfs_stamping *) b;
116 
117     return (vsa == NULL || vsb == NULL || (vsa->v == vsb->v && vsa->id == vsb->id)) ? 0 : 1;
118 }
119 
120 /* --------------------------------------------------------------------------------------------- */
121 
122 static void
vfs_addstamp(struct vfs_class * v,vfsid id)123 vfs_addstamp (struct vfs_class *v, vfsid id)
124 {
125     if ((v->flags & VFSF_LOCAL) == 0 && id != NULL && !vfs_stamp (v, id))
126     {
127         struct vfs_stamping *stamp;
128 
129         stamp = g_new (struct vfs_stamping, 1);
130         stamp->v = v;
131         stamp->id = id;
132         stamp->time = g_get_real_time ();
133 
134         stamps = g_slist_append (stamps, stamp);
135     }
136 }
137 
138 /* --------------------------------------------------------------------------------------------- */
139 /*** public functions ****************************************************************************/
140 /* --------------------------------------------------------------------------------------------- */
141 
142 gboolean
vfs_stamp(struct vfs_class * v,vfsid id)143 vfs_stamp (struct vfs_class *v, vfsid id)
144 {
145     struct vfs_stamping what = {
146         .v = v,
147         .id = id
148     };
149     GSList *stamp;
150     gboolean ret = FALSE;
151 
152     stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare);
153     if (stamp != NULL && stamp->data != NULL)
154     {
155         VFS_STAMPING (stamp->data)->time = g_get_real_time ();
156         ret = TRUE;
157     }
158 
159     return ret;
160 }
161 
162 /* --------------------------------------------------------------------------------------------- */
163 
164 void
vfs_rmstamp(struct vfs_class * v,vfsid id)165 vfs_rmstamp (struct vfs_class *v, vfsid id)
166 {
167     struct vfs_stamping what = {
168         .v = v,
169         .id = id
170     };
171     GSList *stamp;
172 
173     stamp = g_slist_find_custom (stamps, &what, vfs_stamp_compare);
174     if (stamp != NULL)
175     {
176         g_free (stamp->data);
177         stamps = g_slist_delete_link (stamps, stamp);
178     }
179 }
180 
181 /* --------------------------------------------------------------------------------------------- */
182 
183 void
vfs_stamp_path(const vfs_path_t * vpath)184 vfs_stamp_path (const vfs_path_t * vpath)
185 {
186     vfsid id;
187     const vfs_path_element_t *path_element;
188 
189     path_element = vfs_path_get_by_index (vpath, -1);
190 
191     id = vfs_getid (vpath);
192     vfs_addstamp (path_element->class, id);
193 }
194 
195 /* --------------------------------------------------------------------------------------------- */
196 /**
197  * Create a new timestamp item by VFS class and VFS id.
198  */
199 
200 void
vfs_stamp_create(struct vfs_class * vclass,vfsid id)201 vfs_stamp_create (struct vfs_class *vclass, vfsid id)
202 {
203     vfsid nvfsid;
204 
205     ev_vfs_stamp_create_t event_data = { vclass, id, FALSE };
206     const vfs_path_t *vpath;
207     const vfs_path_element_t *path_element;
208 
209     /* There are three directories we have to take care of: current_dir,
210        current_panel->cwd and other_panel->cwd. Athough 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     path_element = vfs_path_get_by_index (vpath, -1);
219 
220     nvfsid = vfs_getid (vpath);
221     vfs_rmstamp (path_element->class, nvfsid);
222 
223     if (!(id == NULL || (path_element->class == 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
vfs_expire(gboolean now)238 vfs_expire (gboolean now)
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_real_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
vfs_timeouts(void)299 vfs_timeouts (void)
300 {
301     return stamps != NULL ? 10 : 0;
302 }
303 
304 /* --------------------------------------------------------------------------------------------- */
305 
306 void
vfs_timeout_handler(void)307 vfs_timeout_handler (void)
308 {
309     vfs_expire (FALSE);
310 }
311 
312 /* --------------------------------------------------------------------------------------------- */
313 
314 void
vfs_release_path(const vfs_path_t * vpath)315 vfs_release_path (const vfs_path_t * vpath)
316 {
317     const vfs_path_element_t *path_element;
318 
319     path_element = vfs_path_get_by_index (vpath, -1);
320     vfs_stamp_create (path_element->class, vfs_getid (vpath));
321 }
322 
323 /* --------------------------------------------------------------------------------------------- */
324 /* Free all data */
325 
326 void
vfs_gc_done(void)327 vfs_gc_done (void)
328 {
329     vfs_expire (TRUE);
330 }
331 
332 /* --------------------------------------------------------------------------------------------- */
333