After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 583330 - poll list of mounted file systems (no mtab support)
poll list of mounted file systems (no mtab support)
Status: RESOLVED FIXED
Product: glib
Classification: Platform
Component: gio
2.20.x
Other NetBSD
: Normal normal
: ---
Assigned To: gtkdev
gtkdev
Depends on:
Blocks:
 
 
Reported: 2009-05-20 13:04 UTC by Thomas Klausner
Modified: 2014-08-27 13:09 UTC
See Also:
GNOME target: ---
GNOME version: 2.25/2.26


Attachments
Patch fixing the problem (4.91 KB, patch)
2009-05-20 13:05 UTC, Thomas Klausner
none Details | Review
Patch using different ifdefs (4.90 KB, patch)
2009-05-25 11:06 UTC, Thomas Klausner
needs-work Details | Review
mounted file system polling - without OS defines (6.16 KB, patch)
2013-11-07 12:37 UTC, Patrick Welche
reviewed Details | Review
Updated patch to poll gunixmounts (6.53 KB, patch)
2014-04-09 10:57 UTC, Patrick Welche
none Details | Review
GUnixMounts: Fall back to polling on systems without mtab (6.72 KB, patch)
2014-08-18 20:53 UTC, Patrick Welche
committed Details | Review
wip (2.05 KB, patch)
2014-08-19 12:13 UTC, Patrick Welche
needs-work Details | Review
example patch (1.70 KB, patch)
2014-08-19 12:58 UTC, Alexander Larsson
none Details | Review
Better algorithm (3.08 KB, patch)
2014-08-19 13:22 UTC, Patrick Welche
none Details | Review
Better algorithm (and deals with unmounting) (3.09 KB, patch)
2014-08-20 09:40 UTC, Patrick Welche
none Details | Review
another version of previous patch - possibly slightly more readable (3.21 KB, patch)
2014-08-23 21:24 UTC, Patrick Welche
needs-work Details | Review
iteration i+1 (3.24 KB, patch)
2014-08-27 12:50 UTC, Patrick Welche
committed Details | Review

Description Thomas Klausner 2009-05-20 13:04:57 UTC
The following patch is a modification of one for FreeBSD ports.
On NetBSD and FreeBSD there are no mtab updates like on other systems, so the list of mounted file systems needs to be polled. See also
http://www.freebsd.org/cgi/cvsweb.cgi/ports/devel/glib20/files/patch-gio_gunixmounts.c?rev=1.1
Comment 1 Thomas Klausner 2009-05-20 13:05:37 UTC
Created attachment 135015 [details] [review]
Patch fixing the problem
Comment 2 Alexander Larsson 2009-05-25 10:42:14 UTC
I don't like how you are using __digital__ and __NetBSD__ as checks. 
It would be much better if it used generic autoconf checks like HAVE_STATFS_F_FSTYPENAME, GETMNTINFO_RETURNS_STATVFS, etc.

The code in this file is already one of the most horrid ifdef spagethi things in existance (which is required unfortunately), so lets not make it worse.
Comment 3 Thomas Klausner 2009-05-25 11:06:23 UTC
Created attachment 135307 [details] [review]
Patch using different ifdefs

Attached version gets rid of __digital__ and __NetBSD__ ifdefs. I didn't find a GETMNTINFO_RETURNS_STATVFS symbol, so I made the code depend on statvfs existence.
Comment 4 Thomas Klausner 2009-05-25 11:07:17 UTC
FYI: Previous patch was compile-tested on NetBSD.
Comment 5 Alexander Larsson 2009-05-25 11:09:52 UTC
I don't mean that these defined exists
We need to add the corresponding checks to configure.in to detect them, using e.g. AC_CHECK_MEMBERS for struct members.
Comment 6 Patrick Welche 2012-01-10 15:12:52 UTC
The trivial configury bits are probably sorted out by Bug 617949, but what of the polling part and the extra mount points?
Comment 7 Brad Smith 2012-03-12 15:40:57 UTC
Just as an FYI this is also in the OpenBSD ports tree and would be required for DragonFly too.
Comment 8 Patrick Welche 2013-11-07 12:37:55 UTC
Created attachment 259177 [details] [review]
mounted file system polling - without OS defines

Reworking of patch making use of Bug 617949 feature tests as per Comment 6.
Comment 9 Colin Walters 2013-11-07 14:01:08 UTC
Review of attachment 259177 [details] [review]:

Can you change the commit message to be something like this:

----
GUnixMounts: Fall back to polling on systems without mtab

This is necessary for many of the BSD family at least.

https://bugzilla.gnome.org/show_bug.cgi?id=583330
----

Basically we've added a prefix saying which code is being changed, and we have a very brief mention of what systems are affected in the message.  The details can be in this bug.

::: gio/gunixmounts.c
@@ +1413,3 @@
+      int i;
+
+      for (i = 0; i < g_list_length (current_mounts); i++)

A little ugly to have N^2 iteration here; hopefully the system has few enough mounts that we won't care.  It's not too hard to iterate two lists at once though...

@@ +1497,3 @@
+      monitor->mount_poller_mounts = _g_get_unix_mounts ();
+      mount_poller_time = (guint64)time (NULL);
+      monitor->mount_poller_source = g_timeout_add_seconds (3, (GSourceFunc)mount_change_poller, monitor);

This will create the source on the default context, whereas the normal GFileMonitor (as used by GUnixMountMonitor) will use the *thread default* context.  This distinction matters for applications which use GLib APIs on threads other than the main thread, and that's a use case that we want to support.

To fix this, do:

GSource *src = g_timeout_source_new_seconds (3);
g_source_set_callback (src, (GSourceFunc)mount_change_poller, monitor, NULL);
g_source_attach (src, g_main_context_get_thread_default ());
g_source_unref (src);
Comment 10 Patrick Welche 2014-04-09 10:57:48 UTC
Created attachment 273879 [details] [review]
Updated patch to poll gunixmounts

As per Colin's detailed review:
- updated the commit message
- both lists are actually just iterated over together in one pass. The pass isn't stopped when a change is found so that the elements of one of the list are freed.
- I had no idea about the thread context issue - fixed as per suggestion!
Comment 11 Patrick Welche 2014-08-18 20:53:01 UTC
Created attachment 283824 [details] [review]
GUnixMounts: Fall back to polling on systems without mtab

Working version of previous...

PASS: mainloop 20 /mainloop/unix-file-poll
Comment 12 Alexander Larsson 2014-08-19 09:00:49 UTC
Review of attachment 283824 [details] [review]:

looks good to me
Comment 13 Alexander Larsson 2014-08-19 09:03:43 UTC
Attachment 283824 [details] pushed as 4f775b7 - GUnixMounts: Fall back to polling on systems without mtab
Comment 14 Emmanuele Bassi (:ebassi) 2014-08-19 09:12:53 UTC
Review of attachment 283824 [details] [review]:

this does not look really good to me, in fairness.

::: gio/gunixmounts.c
@@ +1294,3 @@
+  if (monitor->mount_poller_mounts != NULL)
+    {
+      g_list_foreach (monitor->mount_poller_mounts, (GFunc)g_unix_mount_free, NULL);

foreach()+free() can be replaced by g_list_free_full().

@@ +1399,3 @@
+  current_mounts = _g_get_unix_mounts ();
+
+  if (g_list_length (current_mounts) != g_list_length (mount_monitor->mount_poller_mounts))

these should be placed on temporary variables, since the current_mounts list length is checked twice.

@@ +1408,3 @@
+      int i;
+
+      for (i = 0; i < g_list_length (current_mounts); i++)

ugh, this is *wildly* inefficient: you're iterating over the mounts list for each loop to get the length of the list...

@@ +1413,3 @@
+          GUnixMountEntry *m2;
+
+          m1 = (GUnixMountEntry *)g_list_nth_data (current_mounts, i);

...and then you're iterating over the list again to extract the data at the given index.

this is a linked list, not an array.

@@ +1427,3 @@
+  if (has_changed)
+    {
+      mount_poller_time = (guint64)time (NULL);

why are you using wallclock time, instead of a monotonic clock?

@@ +1492,3 @@
+      monitor->proc_mounts_watch_source = g_timeout_source_new_seconds (3);
+      monitor->mount_poller_mounts = _g_get_unix_mounts ();
+      mount_poller_time = (guint64)time (NULL);

same as above...
Comment 15 Alexander Larsson 2014-08-19 11:13:21 UTC
True, those are all things that could be better. 
Patrick, could you do a new patch fixing those up?
Comment 16 Patrick Welche 2014-08-19 12:13:22 UTC
Created attachment 283871 [details] [review]
wip

Firstly thank you for the review! Colin had mentioned a double loop - I hadn't realised that getting the length of a list was implemented by iterating over it.

I think that this wip patch does what is wanted, but I get coredumps, and what looks like garbage in m2:

(gdb) frame 7
  • #7 mount_change_poller
    at gunixmounts.c line 1404
$1 = {mount_path = 0x7f7fdd7f2dc0 "\370\342\266\334\177\177", 
  device_path = 0x7f7fee7c8e20 "\200\061\362\335\177\177", 
  filesystem_type = 0x0, is_read_only = 0, is_system_internal = 0}

I'm having a hard time spotting the difference...

The committed version seems stable. (I had to put that back to get a working webrowser to be able to upload this wip.diff)

As to monotonic time, it seems that mount_poller_time is being used as a replacement for st_mtime, which is a time in seconds since 1 Jan 1970, so time() seems a better fit. Comments?
Comment 17 Alexander Larsson 2014-08-19 12:49:25 UTC
The time returned from get_mounts_timestamp() should only be compared with g_unix_mounts_changed_since(), so using the monotonic clock should be ok.
Comment 18 Emmanuele Bassi (:ebassi) 2014-08-19 12:53:11 UTC
Review of attachment 283871 [details] [review]:

::: gio/gunixmounts.c
@@ +1396,3 @@
   current_mounts = _g_get_unix_mounts ();
 
+  m1 = (GUnixMountEntry *)g_list_first (current_mounts);

g_list_first() returns a GList*, not the data inside the list. you'd notice that if you didn't cast. in general, you should not be casting from void*/gpointer to and from your data structures.

also, you're iterating on the list of current mounts but freeing elements on the old mounts — which is fine, but then you free the old mounts list. if the two list do not have the same number of elements, there's a potential leak.

you can replace this whole block using:

  GList *new_iter = current_mounts;
  GList *old_iter = mount_monitor->mount_poller_mounts;

  /* check if the lists are different */
  while (new_iter != NULL)
    {
      GUnixMountEntry *m1 = new_iter->data;
      GUnixMountEntry *m2 = old_iter->data;

      if (g_unix_mount_compare (m1, m2) != 0)
        {
          /* no point in continuing */
          has_changed = TRUE;
          break;
        }

      new_iter = new_iter->next;
      old_iter = old_iter->next;
    }

  /* free the old list and assign the new one */
  g_list_free_full (mount_monitor->mount_poller_mounts, (GDestroyNotify) g_unix_mount_free);
  mount_monitor->mount_poller_mounts = current_mounts;
Comment 19 Alexander Larsson 2014-08-19 12:58:46 UTC
Created attachment 283880 [details] [review]
example patch

You want something like this (untested)
Comment 20 Patrick Welche 2014-08-19 13:22:33 UTC
Created attachment 283883 [details] [review]
Better algorithm

This is what I have been testing - I think it is similar enough to both of yours? The only real difference is using free_full would be more readable, but cause another loop, so we free while checking for changes in one go.
Comment 21 Emmanuele Bassi (:ebassi) 2014-08-19 13:27:16 UTC
(In reply to comment #20)

> This is what I have been testing - I think it is similar enough to both of
> yours? The only real difference is using free_full would be more readable, but
> cause another loop, so we free while checking for changes in one go.

no, that's wrong as I explained it my review.

if the lists have different lengths, you're just going to free the list and leak some element.

iterating again to free the contents of the list is inconsequential.
Comment 22 Patrick Welche 2014-08-19 14:17:27 UTC
Unlike in your loop, I am not stopping early - there should not be any leak!
Comment 23 Emmanuele Bassi (:ebassi) 2014-08-19 14:22:58 UTC
(In reply to comment #22)
> Unlike in your loop, I am not stopping early - there should not be any leak!

you're iterating one list (current_mounts) until it's exhausted, but you're freeing the contents of the second list (mount_poller_mounts). if the first list is shorter than the second, the rest of the second list is left dangling; if the second list is shorter than the first, then you're going to check invalid data — which is something that I didn't handle in my code, so you'll have to fix it that as well. the only case where your code works is if the list of mounts is exactly the same length.
Comment 24 Patrick Welche 2014-08-19 14:44:03 UTC
Selectively cut and pasting:

  m2 = g_list_first (mount_monitor->mount_poller_mounts);
  while (m2 != NULL)  
    {
      g_unix_mount_free (m2->data);
      m2 = g_list_next (m2);
    }
  g_list_free (mount_monitor->mount_poller_mounts);

(all I did was skip lines)

I believe that that is identical to

g_list_free_full (mount_monitor->mount_poller_mounts, (GDestroyNotify)g_unix_mount_free);)

it's just that we get to check for changes during the loop.
Comment 25 Alexander Larsson 2014-08-19 18:55:50 UTC
Patrick: 

Yes, but your code also does:
      m1 = g_list_next (m1);

which will keep stepping on the m1 list without checking if it ended.
If you protect against this by doing:
  while (m2 != NULL && m1 != NULL)  

Then you instead will stop early if m1 is null before m2, not freeing everything.

And anyway, its not like you save any work. g_list_free() iterates over the whole list anyway, g_list_free_full is just cleaner and easier to understand.
Comment 26 Patrick Welche 2014-08-20 09:39:12 UTC
(In reply to comment #25)
> Patrick: 
> 
> Yes, but your code also does:
>       m1 = g_list_next (m1);
> 
> which will keep stepping on the m1 list without checking if it ended.

But we don't care about m1, do we. I see that I won't set has_changed in that case, so am about to attach a patch which does this.

> If you protect against this by doing:
>   while (m2 != NULL && m1 != NULL)  

But we don't want to protect against this. We are just looking at m1 (the current list) to see if it is different to m2 (the old list)

> Then you instead will stop early if m1 is null before m2, not freeing
> everything.

Precisely why I haven't been doing that.

> And anyway, its not like you save any work. g_list_free() iterates over the
> whole list anyway, g_list_free_full is just cleaner and easier to understand.

Oh not again - another iteration. OK.
Comment 27 Patrick Welche 2014-08-20 09:40:51 UTC
Created attachment 283954 [details] [review]
Better algorithm (and deals with unmounting)
Comment 28 Patrick Welche 2014-08-23 21:24:09 UTC
Created attachment 284317 [details] [review]
another version of previous patch - possibly slightly more readable
Comment 29 Alexander Larsson 2014-08-25 10:24:29 UTC
Review of attachment 284317 [details] [review]:

::: gio/gunixmounts.c
@@ +1396,3 @@
 
+  new_it = g_list_first (current_mounts);
+  old_it = g_list_first (mount_monitor->mount_poller_mounts);

You don't really need to call g_list_first() here. We're already guaranteed that the things we pass in are the list heads.
Just do new_it = current_mounts; etc.

@@ +1397,3 @@
+  new_it = g_list_first (current_mounts);
+  old_it = g_list_first (mount_monitor->mount_poller_mounts);
+  while (old_it != NULL)

This is still wrong. If the new list has an item added to the end we will never get to it as we only iterate the old list. Thus we will not notice that something changed.

In general the way you do list comparison like this is: iterated until either old or new list ends (while new != NULL && old != NULL), and bail early if the items were non-empty. Then at the end (if you didn't bail early), unless *both* new and old are NULL, then something changed.
Comment 30 Alexander Larsson 2014-08-25 10:25:50 UTC
Review of attachment 284317 [details] [review]:

::: gio/gunixmounts.c
@@ +1397,3 @@
+  new_it = g_list_first (current_mounts);
+  old_it = g_list_first (mount_monitor->mount_poller_mounts);
+  while (old_it != NULL)

Eh, bail early if the items were non-identical i mean...
Comment 31 Patrick Welche 2014-08-27 12:50:16 UTC
Created attachment 284599 [details] [review]
iteration i+1
Comment 32 Alexander Larsson 2014-08-27 13:07:58 UTC
Review of attachment 284599 [details] [review]:

looks good