diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c index 5b71691753..def2f5ef3b 100644 --- a/hw/usb/dev-mtp.c +++ b/hw/usb/dev-mtp.c @@ -15,6 +15,10 @@ #include #include +#ifdef __linux__ +#include +#include "qemu/main-loop.h" +#endif #include "qemu-common.h" #include "qemu/iov.h" @@ -62,6 +66,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 { @@ -84,6 +93,17 @@ enum { EP_EVENT, }; +#ifdef __linux__ +typedef struct MTPMonEntry MTPMonEntry; + +struct MTPMonEntry { + uint32_t event; + uint32_t handle; + + QTAILQ_ENTRY(MTPMonEntry) next; +}; +#endif + struct MTPControl { uint16_t code; uint32_t trans; @@ -108,6 +128,10 @@ struct MTPObject { char *name; char *path; struct stat stat; +#ifdef __linux__ + /* inotify watch cookie */ + int watchfd; +#endif MTPObject *parent; uint32_t nchildren; QLIST_HEAD(, MTPObject) children; @@ -129,6 +153,11 @@ struct MTPState { uint32_t next_handle; QTAILQ_HEAD(, MTPObject) objects; +#ifdef __linux__ + /* inotify descriptor */ + int inotifyfd; + QTAILQ_HEAD(events, MTPMonEntry) events; +#endif }; #define TYPE_USB_MTP "usb-mtp" @@ -372,6 +401,185 @@ static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o, return child; } +#ifdef __linux__ +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 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; + int watchfd = 0; + 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; + } + o->watchfd = watchfd; + 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; + + 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_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 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); +} +#endif + static void usb_mtp_object_readdir(MTPState *s, MTPObject *o) { struct dirent *entry; @@ -386,6 +594,16 @@ static void usb_mtp_object_readdir(MTPState *s, MTPObject *o) if (!dir) { return; } +#ifdef __linux__ + int 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); } @@ -778,11 +996,19 @@ 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); +#ifdef __linux__ + if (usb_mtp_inotify_init(s)) { + fprintf(stderr, "usb-mtp: file monitoring init failed\n"); + } +#endif break; case CMD_CLOSE_SESSION: trace_usb_mtp_op_close_session(s->dev.addr); s->session = 0; s->next_handle = 0; +#ifdef __linux__ + usb_mtp_inotify_cleanup(s); +#endif usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects)); assert(QTAILQ_EMPTY(&s->objects)); break; @@ -908,6 +1134,9 @@ static void usb_mtp_handle_reset(USBDevice *dev) trace_usb_mtp_reset(s->dev.addr); +#ifdef __linux__ + usb_mtp_inotify_cleanup(s); +#endif 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 e2a20cffd0..3686e95d98 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"