inotify: send CHANGES_DONE when new files 'appear'

We generally assume that an IN_CREATE event is the start of a series of
events in which another process is doing this:

  fd = creat (...)         -> IN_CREATE
  write (fd, ..)           -> IN_MODIFY
  write (fd, ..)           -> IN_MODIFY
  close (fd)               -> IN_CLOSE_WRITE

and as such, we use the CHANGES_DONE_HINT event after CREATED in order
to show when this sequence of events has completed (ie: when we receive
IN_CLOSE_WRITE when the user closes the file).

Renaming a file into place is handled by IN_MOVED_FROM so we don't have
to worry about that.

There are many other cases, however, where a new file 'appears' in a
directory in its completed form already, and the kernel reports
IN_CREATE.  Examples include mkdir, mknod, and the creation of
hardlinks.  In these cases, there is no corresponding IN_CLOSE_WRITE
event and the CHANGES_DONE_HINT will have to be emitted by an arbitrary
timeout.

Try to detect some of these cases and report CHANGES_DONE_HINT
immediately.

This is not perfect.  There are some cases that will not be reliably
detected.  An example is if the user makes a hardlink and then
immediately deletes the original (before we can stat the new file).
Another example is if the user creates a file with O_TMPFILE.  In both
of these cases, CHANGES_DONE_HINT will still eventually be delivered via
the timeout.
This commit is contained in:
Ryan Lortie 2015-01-15 11:08:35 -05:00
parent 2737ab3201
commit 3d2d4b8efe

View File

@ -27,6 +27,7 @@
#include <time.h> #include <time.h>
#include <string.h> #include <string.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/stat.h>
/* Just include the local header to stop all the pain */ /* Just include the local header to stop all the pain */
#include <sys/inotify.h> #include <sys/inotify.h>
#include <gio/glocalfilemonitor.h> #include <gio/glocalfilemonitor.h>
@ -197,13 +198,49 @@ ih_event_callback (ik_event_t *event,
/* unpaired event -- no 'other' field */ /* unpaired event -- no 'other' field */
g_file_monitor_source_handle_event (sub->user_data, ih_mask_to_EventFlags (event->mask), g_file_monitor_source_handle_event (sub->user_data, ih_mask_to_EventFlags (event->mask),
event->name, NULL, NULL, event->timestamp); event->name, NULL, NULL, event->timestamp);
if (event->mask & IN_CREATE)
{
const gchar *parent_dir;
gchar *fullname;
struct stat buf;
gint s;
/* The kernel reports IN_CREATE for two types of events:
*
* - creat(), in which case IN_CLOSE_WRITE will come soon; or
* - link(), mkdir(), mknod(), etc., in which case it won't
*
* We can attempt to detect the second case and send the
* CHANGES_DONE immediately so that the user isn't left waiting.
*
* The detection for link() is not 100% reliable since the link
* count could be 1 if the original link was deleted or if
* O_TMPFILE was being used, but in that case the virtual
* CHANGES_DONE will be emitted to close the loop.
*/
parent_dir = _ip_get_path_for_wd (event->wd);
fullname = _ih_fullpath_from_event (event, parent_dir, NULL);
s = stat (fullname, &buf);
g_free (fullname);
/* if it doesn't look like the result of creat()... */
if (s != 0 || !S_ISREG (buf.st_mode) || buf.st_nlink != 1)
g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
event->name, NULL, NULL, event->timestamp);
}
} }
static void static void
ih_not_missing_callback (inotify_sub *sub) ih_not_missing_callback (inotify_sub *sub)
{ {
gint now = g_get_monotonic_time ();
g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CREATED, g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CREATED,
sub->filename, NULL, NULL, g_get_monotonic_time ()); sub->filename, NULL, NULL, now);
g_file_monitor_source_handle_event (sub->user_data, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
sub->filename, NULL, NULL, now);
} }
/* Transforms a inotify event to a GVFS event. */ /* Transforms a inotify event to a GVFS event. */