diff mbox

[v3,3/4] usb-mtp: Add support for inotify based file monitoring

Message ID 1447718794-19812-4-git-send-email-bsd@redhat.com
State New
Headers show

Commit Message

Bandan Das Nov. 17, 2015, 12:06 a.m. UTC
For now, we use inotify watches to track only a small number of
events, namely, add, delete and modify. Note that for delete, the kernel
already deactivates the watch for us and we just need to
take care of modifying our internal state.

inotify is a linux only mechanism. Provide empty stubs and
error out initialization for non linux systems.

Suggested-by: Gerd Hoffman <kraxel@redhat.com>
Signed-off-by: Bandan Das <bsd@redhat.com>
---
 hw/usb/dev-mtp.c | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 trace-events     |   1 +
 2 files changed, 250 insertions(+), 2 deletions(-)

Comments

Gerd Hoffmann Nov. 17, 2015, 12:03 p.m. UTC | #1
Hi,

> +#ifndef __linux__
> +    return 1;
> +#endif

Hmm?  Shouldn't the stubs avoid these kinds of #ifdefs?

> -    QLIST_FOREACH(iter, &o->children, list) {
> +    QLIST_FOREACH_SAFE(iter, &o->children, list, next) {
>          handles[i++] = iter->handle;
>      }

No need for SAFE here, you don't change the list.

> +        if (usb_mtp_inotify_init(s)) {
> +            fprintf(stderr, "usb-mtp: file monitoring init failed\n");
> +        }

I guess we want a function/macro here for linux/non-linux, then report
errors on linux only.  Reporting inotify failures on systems which don't
support inotify in the first place is just noise ...

cheers,
  Gerd
Bandan Das Nov. 17, 2015, 11:48 p.m. UTC | #2
Gerd Hoffmann <kraxel@redhat.com> writes:

>   Hi,
>
>> +#ifndef __linux__
>> +    return 1;
>> +#endif
>
> Hmm?  Shouldn't the stubs avoid these kinds of #ifdefs?
>
>> -    QLIST_FOREACH(iter, &o->children, list) {
>> +    QLIST_FOREACH_SAFE(iter, &o->children, list, next) {
>>          handles[i++] = iter->handle;
>>      }
>
> No need for SAFE here, you don't change the list.

Isn't the SAFE variant better for the case where a inotify delete event occurs
when we are getting the object handles ?

>> +        if (usb_mtp_inotify_init(s)) {
>> +            fprintf(stderr, "usb-mtp: file monitoring init failed\n");
>> +        }
>
> I guess we want a function/macro here for linux/non-linux, then report
> errors on linux only.  Reporting inotify failures on systems which don't
> support inotify in the first place is just noise ...

Agreed, I will rewrite this part.

> cheers,
>   Gerd
Gerd Hoffmann Nov. 18, 2015, 6:55 a.m. UTC | #3
Hi,

> >> -    QLIST_FOREACH(iter, &o->children, list) {
> >> +    QLIST_FOREACH_SAFE(iter, &o->children, list, next) {
> >>          handles[i++] = iter->handle;
> >>      }
> >
> > No need for SAFE here, you don't change the list.
> 
> Isn't the SAFE variant better for the case where a inotify delete event occurs
> when we are getting the object handles ?

Device emulation runs under lock, therefore all serialized, so the
inotify event processing will not run in parallel.

cheers,
  Gerd
diff mbox

Patch

diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
index c39b81a..bc0db2a 100644
--- a/hw/usb/dev-mtp.c
+++ b/hw/usb/dev-mtp.c
@@ -15,15 +15,50 @@ 
 
 #include <sys/stat.h>
 #include <sys/statvfs.h>
+#ifdef __linux__
+#include <sys/inotify.h>
+#endif
 
 #include "qemu-common.h"
 #include "qemu/iov.h"
+#include "qemu/main-loop.h"
 #include "trace.h"
 #include "hw/usb.h"
 #include "hw/usb/desc.h"
 
 /* ----------------------------------------------------------------------- */
 
+/* Empty inotify function stubs for non linux systems */
+#ifndef __linux__
+enum inotify_events {
+    IN_MODIFY   =  0x00000002,
+    IN_CREATE   =  0x00000100,
+    IN_DELETE   =  0x00000200,
+    IN_ISDIR    =  0x40000000,
+    IN_NONBLOCK =  O_NONBLOCK,
+    IN_IGNORED  =  0x00008000,
+};
+
+/* Structure describing an inotify event.  */
+struct inotify_event {
+  int wd;               /* Watch descriptor.  */
+  uint32_t mask;        /* Watch mask.  */
+  uint32_t cookie;      /* Cookie to synchronize two events.  */
+  uint32_t len;         /* Length (including NULs) of name.  */
+  char name __flexarr;  /* Name.  */
+};
+
+static int inotify_init1(int fd)
+{
+    return 0;
+}
+
+static int inotify_add_watch(int fd, char *path, uint32_t mask)
+{
+    return 0;
+}
+#endif
+
 enum mtp_container_type {
     TYPE_COMMAND  = 1,
     TYPE_DATA     = 2,
@@ -62,6 +97,11 @@  enum mtp_code {
     /* format codes */
     FMT_UNDEFINED_OBJECT           = 0x3000,
     FMT_ASSOCIATION                = 0x3001,
+
+    /* event codes */
+    EVT_OBJ_ADDED                  = 0x4002,
+    EVT_OBJ_REMOVED                = 0x4003,
+    EVT_OBJ_INFO_CHANGED           = 0x4007,
 };
 
 typedef struct {
@@ -77,6 +117,7 @@  typedef struct MTPState MTPState;
 typedef struct MTPControl MTPControl;
 typedef struct MTPData MTPData;
 typedef struct MTPObject MTPObject;
+typedef struct MTPMonEntry MTPMonEntry;
 
 enum {
     EP_DATA_IN = 1,
@@ -84,6 +125,13 @@  enum {
     EP_EVENT,
 };
 
+struct MTPMonEntry {
+    uint32_t event;
+    uint32_t handle;
+
+    QTAILQ_ENTRY(MTPMonEntry) next;
+};
+
 struct MTPControl {
     uint16_t     code;
     uint32_t     trans;
@@ -108,6 +156,8 @@  struct MTPObject {
     char         *name;
     char         *path;
     struct stat  stat;
+    /* inotify watch cookie */
+    int          watchfd;
     MTPObject    *parent;
     uint32_t     nchildren;
     QLIST_HEAD(, MTPObject) children;
@@ -121,6 +171,8 @@  struct MTPState {
     char         *root;
     char         *desc;
     uint32_t     flags;
+    /* inotify descriptor */
+    int          inotifyfd;
 
     MTPData      *data_in;
     MTPData      *data_out;
@@ -129,6 +181,7 @@  struct MTPState {
     uint32_t     next_handle;
 
     QTAILQ_HEAD(, MTPObject) objects;
+    QTAILQ_HEAD(events, MTPMonEntry) events;
 };
 
 #define TYPE_USB_MTP "usb-mtp"
@@ -270,6 +323,40 @@  static const USBDesc desc = {
 };
 
 /* ----------------------------------------------------------------------- */
+static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
+                                             char *name, int len)
+{
+    MTPObject *iter;
+
+    QLIST_FOREACH(iter, &parent->children, list) {
+        if (strncmp(iter->name, name, len) == 0) {
+            return iter;
+        }
+    }
+
+    return NULL;
+}
+
+static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
+{
+    MTPObject *iter;
+
+    QTAILQ_FOREACH(iter, &s->objects, next) {
+        if (iter->watchfd == wd) {
+            return iter;
+        }
+    }
+
+    return NULL;
+}
+
+static int usb_mtp_add_watch(int inotifyfd, char *path)
+{
+    uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY |
+        IN_ISDIR;
+
+    return inotify_add_watch(inotifyfd, path, mask);
+}
 
 static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
                                        MTPObject *parent, char *name)
@@ -316,6 +403,23 @@  ignore:
     return NULL;
 }
 
+static void usb_mtp_inotify_cleanup(MTPState *s)
+{
+    MTPMonEntry *e;
+
+    if (!s->inotifyfd) {
+        return;
+    }
+
+    qemu_set_fd_handler(s->inotifyfd, NULL, NULL, s);
+    close(s->inotifyfd);
+
+    QTAILQ_FOREACH(e, &s->events, next) {
+        QTAILQ_REMOVE(&s->events, e, next);
+        g_free(e);
+    }
+}
+
 static void usb_mtp_object_free(MTPState *s, MTPObject *o)
 {
     MTPObject *iter;
@@ -371,10 +475,138 @@  static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
     return child;
 }
 
+static void inotify_watchfn(void *arg)
+{
+    MTPState *s = arg;
+    ssize_t bytes;
+    /* From the man page: atleast one event can be read */
+    int len = sizeof(struct inotify_event) + NAME_MAX + 1;
+    int pos;
+    char buf[len];
+
+    for (;;) {
+        bytes = read(s->inotifyfd, buf, len);
+        pos = 0;
+
+        if (bytes <= 0) {
+            /* Better luck next time */
+            return;
+        }
+
+        /*
+         * TODO: Ignore initiator initiated events.
+         * For now we are good because the store is RO
+         */
+        while (bytes > 0) {
+            char *p = buf + pos;
+            struct inotify_event *event = (struct inotify_event *)p;
+            uint32_t mask = event->mask & (IN_CREATE | IN_DELETE |
+                                           IN_MODIFY | IN_IGNORED);
+            MTPObject *parent = usb_mtp_object_lookup_wd(s, event->wd);
+            MTPMonEntry *entry = NULL;
+            MTPObject *o;
+
+            pos = pos + sizeof(struct inotify_event) + event->len;
+            bytes = bytes - pos;
+
+            if (!parent) {
+                continue;
+            }
+
+            switch (mask) {
+            case IN_CREATE:
+                if (usb_mtp_object_lookup_name
+                    (parent, event->name, event->len)) {
+                    /* Duplicate create event */
+                    continue;
+                }
+                entry = g_new0(MTPMonEntry, 1);
+                entry->handle = s->next_handle;
+                entry->event = EVT_OBJ_ADDED;
+                o = usb_mtp_add_child(s, parent, event->name);
+                if (!o) {
+                    g_free(entry);
+                    continue;
+                }
+                trace_usb_mtp_inotify_event(s->dev.addr, event->name,
+                                            event->mask, "Obj Added");
+                break;
+
+            case IN_DELETE:
+                /*
+                 * The kernel issues a IN_IGNORED event
+                 * when a dir containing a watchpoint is
+                 * deleted, so we don't have to delete the
+                 * watchpoint
+                 */
+                o = usb_mtp_object_lookup_name(parent, event->name, event->len);
+                if (!o) {
+                    continue;
+                }
+                entry = g_new0(MTPMonEntry, 1);
+                entry->handle = o->handle;
+                entry->event = EVT_OBJ_REMOVED;
+                usb_mtp_object_free(s, o);
+                trace_usb_mtp_inotify_event(s->dev.addr, o->path,
+                                      event->mask, "Obj Deleted");
+                break;
+
+            case IN_MODIFY:
+                o = usb_mtp_object_lookup_name(parent, event->name, event->len);
+                if (!o) {
+                    continue;
+                }
+                entry = g_new0(MTPMonEntry, 1);
+                entry->handle = o->handle;
+                entry->event = EVT_OBJ_INFO_CHANGED;
+                trace_usb_mtp_inotify_event(s->dev.addr, o->path,
+                                      event->mask, "Obj Modified");
+                break;
+
+            case IN_IGNORED:
+                o = usb_mtp_object_lookup_name(parent, event->name, event->len);
+                trace_usb_mtp_inotify_event(s->dev.addr, o->path,
+                                      event->mask, "Obj ignored");
+                break;
+
+            default:
+                fprintf(stderr, "usb-mtp: failed to parse inotify event\n");
+                continue;
+            }
+
+            if (entry) {
+                QTAILQ_INSERT_HEAD(&s->events, entry, next);
+            }
+        }
+    }
+}
+
+static int usb_mtp_inotify_init(MTPState *s)
+{
+    int fd;
+
+#ifndef __linux__
+    return 1;
+#endif
+
+    fd = inotify_init1(IN_NONBLOCK);
+    if (fd == -1) {
+        return 1;
+    }
+
+    QTAILQ_INIT(&s->events);
+    s->inotifyfd = fd;
+
+    qemu_set_fd_handler(fd, inotify_watchfn, NULL, s);
+
+    return 0;
+}
+
 static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
 {
     struct dirent *entry;
     DIR *dir;
+    int watchfd;
 
     if (o->have_children) {
         return;
@@ -385,6 +617,16 @@  static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
     if (!dir) {
         return;
     }
+#ifdef __linux__
+    watchfd = usb_mtp_add_watch(s->inotifyfd, o->path);
+    if (watchfd == -1) {
+        fprintf(stderr, "usb-mtp: failed to add watch for %s\n", o->path);
+    } else {
+        trace_usb_mtp_inotify_event(s->dev.addr, o->path,
+                                    0, "Watch Added");
+        o->watchfd = watchfd;
+    }
+#endif
     while ((entry = readdir(dir)) != NULL) {
         usb_mtp_add_child(s, o, entry->d_name);
     }
@@ -639,11 +881,11 @@  static MTPData *usb_mtp_get_object_handles(MTPState *s, MTPControl *c,
 {
     MTPData *d = usb_mtp_data_alloc(c);
     uint32_t i = 0, handles[o->nchildren];
-    MTPObject *iter;
+    MTPObject *iter, *next;
 
     trace_usb_mtp_op_get_object_handles(s->dev.addr, o->handle, o->path);
 
-    QLIST_FOREACH(iter, &o->children, list) {
+    QLIST_FOREACH_SAFE(iter, &o->children, list, next) {
         handles[i++] = iter->handle;
     }
     assert(i == o->nchildren);
@@ -777,11 +1019,15 @@  static void usb_mtp_command(MTPState *s, MTPControl *c)
         trace_usb_mtp_op_open_session(s->dev.addr);
         s->session = c->argv[0];
         usb_mtp_object_alloc(s, s->next_handle++, NULL, s->root);
+        if (usb_mtp_inotify_init(s)) {
+            fprintf(stderr, "usb-mtp: file monitoring init failed\n");
+        }
         break;
     case CMD_CLOSE_SESSION:
         trace_usb_mtp_op_close_session(s->dev.addr);
         s->session = 0;
         s->next_handle = 0;
+        usb_mtp_inotify_cleanup(s);
         usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
         assert(QTAILQ_EMPTY(&s->objects));
         break;
@@ -907,6 +1153,7 @@  static void usb_mtp_handle_reset(USBDevice *dev)
 
     trace_usb_mtp_reset(s->dev.addr);
 
+    usb_mtp_inotify_cleanup(s);
     usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
     s->session = 0;
     usb_mtp_data_free(s->data_in);
diff --git a/trace-events b/trace-events
index a7d1da9..9a986a4 100644
--- a/trace-events
+++ b/trace-events
@@ -553,6 +553,7 @@  usb_mtp_op_unknown(int dev, uint32_t code) "dev %d, command code 0x%x"
 usb_mtp_object_alloc(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
 usb_mtp_object_free(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
 usb_mtp_add_child(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
+usb_mtp_inotify_event(int dev, const char *path, uint32_t mask, const char *s) "dev %d, path %s mask 0x%x event %s"
 
 # hw/usb/host-libusb.c
 usb_host_open_started(int bus, int addr) "dev %d:%d"