*/
#include "qemu/osdep.h"
+#include "qemu-common.h"
#include "qapi/error.h"
+#include "qemu/error-report.h"
#include <wchar.h>
#include <dirent.h>
#include <sys/statvfs.h>
-#ifdef CONFIG_INOTIFY1
-#include <sys/inotify.h>
-#include "qemu/main-loop.h"
-#endif
-#include "qemu-common.h"
+
#include "qemu/iov.h"
+#include "qemu/module.h"
+#include "qemu/filemonitor.h"
#include "trace.h"
#include "hw/usb.h"
#include "desc.h"
EP_EVENT,
};
-#ifdef CONFIG_INOTIFY1
typedef struct MTPMonEntry MTPMonEntry;
struct MTPMonEntry {
QTAILQ_ENTRY(MTPMonEntry) next;
};
-#endif
struct MTPControl {
uint16_t code;
char *name;
char *path;
struct stat stat;
-#ifdef CONFIG_INOTIFY1
- /* inotify watch cookie */
- int watchfd;
-#endif
+ /* file monitor watch id */
+ int64_t watchid;
MTPObject *parent;
uint32_t nchildren;
QLIST_HEAD(, MTPObject) children;
bool readonly;
QTAILQ_HEAD(, MTPObject) objects;
-#ifdef CONFIG_INOTIFY1
- /* inotify descriptor */
- int inotifyfd;
+ QFileMonitor *file_monitor;
QTAILQ_HEAD(, MTPMonEntry) events;
-#endif
/* Responder is expecting a write operation */
bool write_pending;
struct {
uint32_t assoc_desc;
uint32_t seq_no; /*unused*/
uint8_t length; /*part of filename field*/
- uint16_t filename[0];
+ uint8_t filename[0]; /* UTF-16 encoded */
char date_created[0]; /*unused*/
char date_modified[0]; /*unused*/
char keywords[0]; /*unused*/
/* ----------------------------------------------------------------------- */
static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
- MTPObject *parent, char *name)
+ MTPObject *parent, const char *name)
{
MTPObject *o = g_new0(MTPObject, 1);
goto ignore;
}
+ o->watchid = -1;
o->handle = handle;
o->parent = parent;
o->name = g_strdup(name);
trace_usb_mtp_object_free(s->dev.addr, o->handle, o->path);
+ if (o->watchid != -1 && s->file_monitor) {
+ qemu_file_monitor_remove_watch(s->file_monitor, o->path, o->watchid);
+ }
+
QTAILQ_REMOVE(&s->objects, o, next);
if (o->parent) {
QLIST_REMOVE(o, list);
}
static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
- char *name)
+ const char *name)
{
MTPObject *child =
usb_mtp_object_alloc(s, s->next_handle++, o, name);
}
static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
- char *name, int len)
+ const char *name, int len)
{
MTPObject *iter;
+ if (len == -1) {
+ len = strlen(name);
+ }
+
QLIST_FOREACH(iter, &parent->children, list) {
if (strncmp(iter->name, name, len) == 0) {
return iter;
return NULL;
}
-#ifdef CONFIG_INOTIFY1
-static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
+static MTPObject *usb_mtp_object_lookup_id(MTPState *s, int64_t id)
{
MTPObject *iter;
QTAILQ_FOREACH(iter, &s->objects, next) {
- if (iter->watchfd == wd) {
+ if (iter->watchid == id) {
return iter;
}
}
return NULL;
}
-static void inotify_watchfn(void *arg)
+static void file_monitor_event(int64_t id,
+ QFileMonitorEvent ev,
+ const char *name,
+ void *opaque)
{
- MTPState *s = arg;
- ssize_t bytes;
- /* From the man page: atleast one event can be read */
- int pos;
- char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
-
- for (;;) {
- bytes = read(s->inotifyfd, buf, sizeof(buf));
- pos = 0;
-
- if (bytes <= 0) {
- /* Better luck next time */
+ MTPState *s = opaque;
+ MTPObject *parent = usb_mtp_object_lookup_id(s, id);
+ MTPMonEntry *entry = NULL;
+ MTPObject *o;
+
+ if (!parent) {
+ return;
+ }
+
+ switch (ev) {
+ case QFILE_MONITOR_EVENT_CREATED:
+ if (usb_mtp_object_lookup_name(parent, name, -1)) {
+ /* Duplicate create event */
+ return;
+ }
+ entry = g_new0(MTPMonEntry, 1);
+ entry->handle = s->next_handle;
+ entry->event = EVT_OBJ_ADDED;
+ o = usb_mtp_add_child(s, parent, name);
+ if (!o) {
+ g_free(entry);
return;
}
+ trace_usb_mtp_file_monitor_event(s->dev.addr, name, "Obj Added");
+ break;
+ case QFILE_MONITOR_EVENT_DELETED:
/*
- * TODO: Ignore initiator initiated events.
- * For now we are good because the store is RO
+ * The kernel issues a IN_IGNORED event
+ * when a dir containing a watchpoint is
+ * deleted, so we don't have to delete the
+ * watchpoint
*/
- 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;
- trace_usb_mtp_inotify_event(s->dev.addr, o->path,
- event->mask, "Obj Deleted");
- usb_mtp_object_free(s, o);
- 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:
- trace_usb_mtp_inotify_event(s->dev.addr, parent->path,
- event->mask, "Obj parent dir ignored");
- break;
-
- default:
- fprintf(stderr, "usb-mtp: failed to parse inotify event\n");
- continue;
- }
-
- if (entry) {
- QTAILQ_INSERT_HEAD(&s->events, entry, next);
- }
+ o = usb_mtp_object_lookup_name(parent, name, -1);
+ if (!o) {
+ return;
}
- }
-}
+ entry = g_new0(MTPMonEntry, 1);
+ entry->handle = o->handle;
+ entry->event = EVT_OBJ_REMOVED;
+ trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Deleted");
+ usb_mtp_object_free(s, o);
+ break;
-static int usb_mtp_inotify_init(MTPState *s)
-{
- int fd;
+ case QFILE_MONITOR_EVENT_MODIFIED:
+ o = usb_mtp_object_lookup_name(parent, name, -1);
+ if (!o) {
+ return;
+ }
+ entry = g_new0(MTPMonEntry, 1);
+ entry->handle = o->handle;
+ entry->event = EVT_OBJ_INFO_CHANGED;
+ trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Modified");
+ break;
- fd = inotify_init1(IN_NONBLOCK);
- if (fd == -1) {
- return 1;
- }
+ case QFILE_MONITOR_EVENT_IGNORED:
+ trace_usb_mtp_file_monitor_event(s->dev.addr, parent->path,
+ "Obj parent dir ignored");
+ break;
- QTAILQ_INIT(&s->events);
- s->inotifyfd = fd;
+ case QFILE_MONITOR_EVENT_ATTRIBUTES:
+ break;
- qemu_set_fd_handler(fd, inotify_watchfn, NULL, s);
+ default:
+ g_assert_not_reached();
+ }
- return 0;
+ if (entry) {
+ QTAILQ_INSERT_HEAD(&s->events, entry, next);
+ }
}
-static void usb_mtp_inotify_cleanup(MTPState *s)
+static void usb_mtp_file_monitor_cleanup(MTPState *s)
{
MTPMonEntry *e, *p;
- if (!s->inotifyfd) {
- return;
- }
-
- qemu_set_fd_handler(s->inotifyfd, NULL, NULL, s);
- close(s->inotifyfd);
-
QTAILQ_FOREACH_SAFE(e, &s->events, next, p) {
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);
+ qemu_file_monitor_free(s->file_monitor);
+ s->file_monitor = NULL;
}
-#endif
+
static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
{
struct dirent *entry;
DIR *dir;
int fd;
+ Error *err = NULL;
if (o->have_children) {
return;
close(fd);
return;
}
-#ifdef CONFIG_INOTIFY1
- 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;
+
+ if (s->file_monitor) {
+ int64_t id = qemu_file_monitor_add_watch(s->file_monitor, o->path, NULL,
+ file_monitor_event, s, &err);
+ if (id == -1) {
+ error_report("usb-mtp: failed to add watch for %s: %s", o->path,
+ error_get_pretty(err));
+ error_free(err);
+ } else {
+ trace_usb_mtp_file_monitor_event(s->dev.addr, o->path,
+ "Watch Added");
+ o->watchid = id;
+ }
}
-#endif
+
while ((entry = readdir(dir)) != NULL) {
usb_mtp_add_child(s, o, entry->d_name);
}
return d;
}
-/* Return correct return code for a delete event */
+/*
+ * Return values when object @o is deleted.
+ * If at least one of the deletions succeeded,
+ * DELETE_SUCCESS is set and if at least one
+ * of the deletions failed, DELETE_FAILURE is
+ * set. Both bits being set (DELETE_PARTIAL)
+ * signifies a RES_PARTIAL_DELETE being sent
+ * back to the initiator.
+ */
enum {
- ALL_DELETE,
- PARTIAL_DELETE,
- READ_ONLY,
+ DELETE_SUCCESS = (1 << 0),
+ DELETE_FAILURE = (1 << 1),
+ DELETE_PARTIAL = (DELETE_FAILURE | DELETE_SUCCESS),
};
-/* Assumes that children, if any, have been already freed */
-static void usb_mtp_object_free_one(MTPState *s, MTPObject *o)
-{
-#ifndef CONFIG_INOTIFY1
- assert(o->nchildren == 0);
- QTAILQ_REMOVE(&s->objects, o, next);
- g_free(o->name);
- g_free(o->path);
- g_free(o);
-#endif
-}
-
static int usb_mtp_deletefn(MTPState *s, MTPObject *o, uint32_t trans)
{
MTPObject *iter, *iter2;
- bool partial_delete = false;
- bool success = false;
+ int ret = 0;
/*
* TODO: Add support for Protection Status
QLIST_FOREACH(iter, &o->children, list) {
if (iter->format == FMT_ASSOCIATION) {
QLIST_FOREACH(iter2, &iter->children, list) {
- usb_mtp_deletefn(s, iter2, trans);
+ ret |= usb_mtp_deletefn(s, iter2, trans);
}
}
}
if (o->format == FMT_UNDEFINED_OBJECT) {
if (remove(o->path)) {
- partial_delete = true;
+ ret |= DELETE_FAILURE;
} else {
- usb_mtp_object_free_one(s, o);
- success = true;
+ usb_mtp_object_free(s, o);
+ ret |= DELETE_SUCCESS;
}
- }
-
- if (o->format == FMT_ASSOCIATION) {
+ } else if (o->format == FMT_ASSOCIATION) {
if (rmdir(o->path)) {
- partial_delete = true;
+ ret |= DELETE_FAILURE;
} else {
- usb_mtp_object_free_one(s, o);
- success = true;
+ usb_mtp_object_free(s, o);
+ ret |= DELETE_SUCCESS;
}
}
- if (success && partial_delete) {
- return PARTIAL_DELETE;
- }
- if (!success && partial_delete) {
- return READ_ONLY;
- }
- return ALL_DELETE;
+ return ret;
}
static void usb_mtp_object_delete(MTPState *s, uint32_t handle,
}
ret = usb_mtp_deletefn(s, o, trans);
- if (ret == PARTIAL_DELETE) {
- usb_mtp_queue_result(s, RES_PARTIAL_DELETE,
- trans, 0, 0, 0, 0);
- return;
- } else if (ret == READ_ONLY) {
- usb_mtp_queue_result(s, RES_STORE_READ_ONLY, trans,
- 0, 0, 0, 0);
- return;
- } else {
+ switch (ret) {
+ case DELETE_SUCCESS:
usb_mtp_queue_result(s, RES_OK, trans,
0, 0, 0, 0);
- return;
+ break;
+ case DELETE_FAILURE:
+ usb_mtp_queue_result(s, RES_PARTIAL_DELETE,
+ trans, 0, 0, 0, 0);
+ break;
+ case DELETE_PARTIAL:
+ usb_mtp_queue_result(s, RES_PARTIAL_DELETE,
+ trans, 0, 0, 0, 0);
+ break;
+ default:
+ g_assert_not_reached();
}
+
+ return;
}
static void usb_mtp_command(MTPState *s, MTPControl *c)
MTPData *data_in = NULL;
MTPObject *o = NULL;
uint32_t nres = 0, res0 = 0;
+ Error *err = NULL;
/* sanity checks */
if (c->code >= CMD_CLOSE_SESSION && s->session == 0) {
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 CONFIG_INOTIFY1
- if (usb_mtp_inotify_init(s)) {
- fprintf(stderr, "usb-mtp: file monitoring init failed\n");
+
+ s->file_monitor = qemu_file_monitor_new(&err);
+ if (err) {
+ error_report("usb-mtp: file monitoring init failed: %s",
+ error_get_pretty(err));
+ error_free(err);
+ } else {
+ QTAILQ_INIT(&s->events);
}
-#endif
break;
case CMD_CLOSE_SESSION:
trace_usb_mtp_op_close_session(s->dev.addr);
s->session = 0;
s->next_handle = 0;
-#ifdef CONFIG_INOTIFY1
- usb_mtp_inotify_cleanup(s);
-#endif
+ usb_mtp_file_monitor_cleanup(s);
usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
assert(QTAILQ_EMPTY(&s->objects));
break;
trace_usb_mtp_reset(s->dev.addr);
-#ifdef CONFIG_INOTIFY1
- usb_mtp_inotify_cleanup(s);
-#endif
+ usb_mtp_file_monitor_cleanup(s);
usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
s->session = 0;
usb_mtp_data_free(s->data_in);
fprintf(stderr, "%s\n", __func__);
}
-static char *utf16_to_str(uint8_t len, uint16_t *arr)
+static char *utf16_to_str(uint8_t len, uint8_t *str16)
{
wchar_t *wstr = g_new0(wchar_t, len + 1);
int count, dlen;
for (count = 0; count < len; count++) {
/* FIXME: not working for surrogate pairs */
- wstr[count] = (wchar_t)arr[count];
+ wstr[count] = lduw_le_p(str16 + (count * 2));
}
wstr[count] = 0;
return ret;
}
-static void usb_mtp_update_object(MTPObject *parent, char *name)
+static int usb_mtp_update_object(MTPObject *parent, char *name)
{
+ int ret = 0;
+
MTPObject *o =
usb_mtp_object_lookup_name(parent, name, strlen(name));
if (o) {
- lstat(o->path, &o->stat);
+ ret = lstat(o->path, &o->stat);
}
+
+ return ret;
}
-static void usb_mtp_write_data(MTPState *s)
+static void usb_mtp_write_data(MTPState *s, uint32_t handle)
{
MTPData *d = s->data_out;
MTPObject *parent =
char *path = NULL;
uint64_t rc;
mode_t mask = 0644;
+ int ret = 0;
assert(d != NULL);
if (!parent || !s->write_pending) {
usb_mtp_queue_result(s, RES_INVALID_OBJECTINFO, d->trans,
0, 0, 0, 0);
- return;
+ return;
}
if (s->dataset.filename) {
path = g_strdup_printf("%s/%s", parent->path, s->dataset.filename);
if (s->dataset.format == FMT_ASSOCIATION) {
- d->fd = mkdir(path, mask);
- goto free;
+ ret = mkdir(path, mask);
+ if (!ret) {
+ usb_mtp_queue_result(s, RES_OK, d->trans, 3,
+ QEMU_STORAGE_ID,
+ s->dataset.parent_handle,
+ handle);
+ goto close;
+ }
+ goto done;
}
+
d->fd = open(path, O_CREAT | O_WRONLY |
O_CLOEXEC | O_NOFOLLOW, mask);
if (d->fd == -1) {
- usb_mtp_queue_result(s, RES_STORE_FULL, d->trans,
- 0, 0, 0, 0);
+ ret = 1;
goto done;
}
/* Return success if initiator sent 0 sized data */
if (!s->dataset.size) {
- goto success;
+ goto done;
}
if (d->length != MTP_WRITE_BUF_SZ && !d->pending) {
d->write_status = WRITE_END;
rc = write_retry(d->fd, d->data, d->data_offset,
d->offset - d->data_offset);
if (rc != d->data_offset) {
- usb_mtp_queue_result(s, RES_STORE_FULL, d->trans,
- 0, 0, 0, 0);
+ ret = 1;
goto done;
}
if (d->write_status != WRITE_END) {
+ g_free(path);
return;
} else {
- /* Only for < 4G file sizes */
- if (s->dataset.size != 0xFFFFFFFF && d->offset != s->dataset.size) {
+ /*
+ * Return an incomplete transfer if file size doesn't match
+ * for < 4G file or if lstat fails which will result in an incorrect
+ * file size
+ */
+ if ((s->dataset.size != 0xFFFFFFFF &&
+ d->offset != s->dataset.size) ||
+ usb_mtp_update_object(parent, s->dataset.filename)) {
usb_mtp_queue_result(s, RES_INCOMPLETE_TRANSFER, d->trans,
0, 0, 0, 0);
- goto done;
+ goto close;
}
- usb_mtp_update_object(parent, s->dataset.filename);
}
}
-success:
- usb_mtp_queue_result(s, RES_OK, d->trans,
- 0, 0, 0, 0);
-
done:
+ if (ret) {
+ usb_mtp_queue_result(s, RES_STORE_FULL, d->trans,
+ 0, 0, 0, 0);
+ } else {
+ usb_mtp_queue_result(s, RES_OK, d->trans,
+ 0, 0, 0, 0);
+ }
+close:
/*
* The write dataset is kept around and freed only
* on success or if another write request comes in
*/
if (d->fd != -1) {
close(d->fd);
+ d->fd = -1;
}
-free:
g_free(s->dataset.filename);
s->dataset.size = 0;
g_free(path);
MTPObject *o;
MTPObject *p = usb_mtp_object_lookup(s, s->dataset.parent_handle);
uint32_t next_handle = s->next_handle;
+ size_t filename_chars = dlen - offsetof(ObjectInfo, filename);
+
+ /*
+ * filename is utf-16. We're intentionally doing
+ * integer division to truncate if malicious guest
+ * sent an odd number of bytes.
+ */
+ filename_chars /= 2;
assert(!s->write_pending);
assert(p != NULL);
- filename = utf16_to_str(MIN(dataset->length,
- dlen - offsetof(ObjectInfo, filename)),
+ filename = utf16_to_str(MIN(dataset->length, filename_chars),
dataset->filename);
if (strchr(filename, '/')) {
return;
}
- o = usb_mtp_object_lookup_name(p, filename, dataset->length);
+ o = usb_mtp_object_lookup_name(p, filename, -1);
if (o != NULL) {
next_handle = o->handle;
}
s->write_pending = true;
if (s->dataset.format == FMT_ASSOCIATION) {
- usb_mtp_write_data(s);
- /* next_handle will be allocated to the newly created dir */
- if (d->fd == -1) {
- usb_mtp_queue_result(s, RES_STORE_FULL, d->trans,
- 0, 0, 0, 0);
- return;
- }
- d->fd = -1;
+ usb_mtp_write_data(s, next_handle);
+ } else {
+ usb_mtp_queue_result(s, RES_OK, d->trans, 3, QEMU_STORAGE_ID,
+ s->dataset.parent_handle, next_handle);
}
-
- usb_mtp_queue_result(s, RES_OK, d->trans, 3, QEMU_STORAGE_ID,
- s->dataset.parent_handle, next_handle);
}
static void usb_mtp_get_data(MTPState *s, mtp_container *container,
} else {
d->write_status = WRITE_START;
}
- usb_mtp_write_data(s);
+ usb_mtp_write_data(s, 0);
usb_mtp_data_free(s->data_out);
s->data_out = NULL;
return;
}
if (d->data_offset == d->length) {
d->pending = true;
- usb_mtp_write_data(s);
+ usb_mtp_write_data(s, 0);
}
break;
default:
}
break;
case EP_EVENT:
-#ifdef CONFIG_INOTIFY1
if (!QTAILQ_EMPTY(&s->events)) {
struct MTPMonEntry *e = QTAILQ_LAST(&s->events);
uint32_t handle;
g_free(e);
return;
}
-#endif
p->status = USB_RET_NAK;
return;
default: