| /* |
| * QEMU monitor file descriptor passing |
| * |
| * Copyright (c) 2003-2004 Fabrice Bellard |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "monitor-internal.h" |
| #include "qapi/error.h" |
| #include "qapi/qapi-commands-misc.h" |
| #include "qapi/qmp/qerror.h" |
| #include "qemu/ctype.h" |
| #include "qemu/cutils.h" |
| #include "sysemu/runstate.h" |
| |
| /* file descriptors passed via SCM_RIGHTS */ |
| typedef struct mon_fd_t mon_fd_t; |
| struct mon_fd_t { |
| char *name; |
| int fd; |
| QLIST_ENTRY(mon_fd_t) next; |
| }; |
| |
| /* file descriptor associated with a file descriptor set */ |
| typedef struct MonFdsetFd MonFdsetFd; |
| struct MonFdsetFd { |
| int fd; |
| bool removed; |
| char *opaque; |
| QLIST_ENTRY(MonFdsetFd) next; |
| }; |
| |
| /* file descriptor set containing fds passed via SCM_RIGHTS */ |
| typedef struct MonFdset MonFdset; |
| struct MonFdset { |
| int64_t id; |
| QLIST_HEAD(, MonFdsetFd) fds; |
| QLIST_HEAD(, MonFdsetFd) dup_fds; |
| QLIST_ENTRY(MonFdset) next; |
| }; |
| |
| /* Protects mon_fdsets */ |
| static QemuMutex mon_fdsets_lock; |
| static QLIST_HEAD(, MonFdset) mon_fdsets; |
| |
| void qmp_getfd(const char *fdname, Error **errp) |
| { |
| Monitor *cur_mon = monitor_cur(); |
| mon_fd_t *monfd; |
| int fd, tmp_fd; |
| |
| fd = qemu_chr_fe_get_msgfd(&cur_mon->chr); |
| if (fd == -1) { |
| error_setg(errp, "No file descriptor supplied via SCM_RIGHTS"); |
| return; |
| } |
| |
| if (qemu_isdigit(fdname[0])) { |
| close(fd); |
| error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "fdname", |
| "a name not starting with a digit"); |
| return; |
| } |
| |
| QEMU_LOCK_GUARD(&cur_mon->mon_lock); |
| QLIST_FOREACH(monfd, &cur_mon->fds, next) { |
| if (strcmp(monfd->name, fdname) != 0) { |
| continue; |
| } |
| |
| tmp_fd = monfd->fd; |
| monfd->fd = fd; |
| /* Make sure close() is outside critical section */ |
| close(tmp_fd); |
| return; |
| } |
| |
| monfd = g_new0(mon_fd_t, 1); |
| monfd->name = g_strdup(fdname); |
| monfd->fd = fd; |
| |
| QLIST_INSERT_HEAD(&cur_mon->fds, monfd, next); |
| } |
| |
| void qmp_closefd(const char *fdname, Error **errp) |
| { |
| Monitor *cur_mon = monitor_cur(); |
| mon_fd_t *monfd; |
| int tmp_fd; |
| |
| qemu_mutex_lock(&cur_mon->mon_lock); |
| QLIST_FOREACH(monfd, &cur_mon->fds, next) { |
| if (strcmp(monfd->name, fdname) != 0) { |
| continue; |
| } |
| |
| QLIST_REMOVE(monfd, next); |
| tmp_fd = monfd->fd; |
| g_free(monfd->name); |
| g_free(monfd); |
| qemu_mutex_unlock(&cur_mon->mon_lock); |
| /* Make sure close() is outside critical section */ |
| close(tmp_fd); |
| return; |
| } |
| |
| qemu_mutex_unlock(&cur_mon->mon_lock); |
| error_setg(errp, "File descriptor named '%s' not found", fdname); |
| } |
| |
| int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp) |
| { |
| mon_fd_t *monfd; |
| |
| QEMU_LOCK_GUARD(&mon->mon_lock); |
| QLIST_FOREACH(monfd, &mon->fds, next) { |
| int fd; |
| |
| if (strcmp(monfd->name, fdname) != 0) { |
| continue; |
| } |
| |
| fd = monfd->fd; |
| assert(fd >= 0); |
| |
| /* caller takes ownership of fd */ |
| QLIST_REMOVE(monfd, next); |
| g_free(monfd->name); |
| g_free(monfd); |
| |
| return fd; |
| } |
| |
| error_setg(errp, "File descriptor named '%s' has not been found", fdname); |
| return -1; |
| } |
| |
| static void monitor_fdset_cleanup(MonFdset *mon_fdset) |
| { |
| MonFdsetFd *mon_fdset_fd; |
| MonFdsetFd *mon_fdset_fd_next; |
| |
| QLIST_FOREACH_SAFE(mon_fdset_fd, &mon_fdset->fds, next, mon_fdset_fd_next) { |
| if ((mon_fdset_fd->removed || |
| (QLIST_EMPTY(&mon_fdset->dup_fds) && mon_refcount == 0)) && |
| runstate_is_running()) { |
| close(mon_fdset_fd->fd); |
| g_free(mon_fdset_fd->opaque); |
| QLIST_REMOVE(mon_fdset_fd, next); |
| g_free(mon_fdset_fd); |
| } |
| } |
| |
| if (QLIST_EMPTY(&mon_fdset->fds) && QLIST_EMPTY(&mon_fdset->dup_fds)) { |
| QLIST_REMOVE(mon_fdset, next); |
| g_free(mon_fdset); |
| } |
| } |
| |
| void monitor_fdsets_cleanup(void) |
| { |
| MonFdset *mon_fdset; |
| MonFdset *mon_fdset_next; |
| |
| QEMU_LOCK_GUARD(&mon_fdsets_lock); |
| QLIST_FOREACH_SAFE(mon_fdset, &mon_fdsets, next, mon_fdset_next) { |
| monitor_fdset_cleanup(mon_fdset); |
| } |
| } |
| |
| AddfdInfo *qmp_add_fd(bool has_fdset_id, int64_t fdset_id, |
| const char *opaque, Error **errp) |
| { |
| int fd; |
| Monitor *mon = monitor_cur(); |
| AddfdInfo *fdinfo; |
| |
| fd = qemu_chr_fe_get_msgfd(&mon->chr); |
| if (fd == -1) { |
| error_setg(errp, "No file descriptor supplied via SCM_RIGHTS"); |
| goto error; |
| } |
| |
| fdinfo = monitor_fdset_add_fd(fd, has_fdset_id, fdset_id, opaque, errp); |
| if (fdinfo) { |
| return fdinfo; |
| } |
| |
| error: |
| if (fd != -1) { |
| close(fd); |
| } |
| return NULL; |
| } |
| |
| void qmp_remove_fd(int64_t fdset_id, bool has_fd, int64_t fd, Error **errp) |
| { |
| MonFdset *mon_fdset; |
| MonFdsetFd *mon_fdset_fd; |
| char fd_str[60]; |
| |
| QEMU_LOCK_GUARD(&mon_fdsets_lock); |
| QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { |
| if (mon_fdset->id != fdset_id) { |
| continue; |
| } |
| QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) { |
| if (has_fd) { |
| if (mon_fdset_fd->fd != fd) { |
| continue; |
| } |
| mon_fdset_fd->removed = true; |
| break; |
| } else { |
| mon_fdset_fd->removed = true; |
| } |
| } |
| if (has_fd && !mon_fdset_fd) { |
| goto error; |
| } |
| monitor_fdset_cleanup(mon_fdset); |
| return; |
| } |
| |
| error: |
| if (has_fd) { |
| snprintf(fd_str, sizeof(fd_str), "fdset-id:%" PRId64 ", fd:%" PRId64, |
| fdset_id, fd); |
| } else { |
| snprintf(fd_str, sizeof(fd_str), "fdset-id:%" PRId64, fdset_id); |
| } |
| error_setg(errp, "File descriptor named '%s' not found", fd_str); |
| } |
| |
| FdsetInfoList *qmp_query_fdsets(Error **errp) |
| { |
| MonFdset *mon_fdset; |
| MonFdsetFd *mon_fdset_fd; |
| FdsetInfoList *fdset_list = NULL; |
| |
| QEMU_LOCK_GUARD(&mon_fdsets_lock); |
| QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { |
| FdsetInfo *fdset_info = g_malloc0(sizeof(*fdset_info)); |
| |
| fdset_info->fdset_id = mon_fdset->id; |
| |
| QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) { |
| FdsetFdInfo *fdsetfd_info; |
| |
| fdsetfd_info = g_malloc0(sizeof(*fdsetfd_info)); |
| fdsetfd_info->fd = mon_fdset_fd->fd; |
| fdsetfd_info->opaque = g_strdup(mon_fdset_fd->opaque); |
| |
| QAPI_LIST_PREPEND(fdset_info->fds, fdsetfd_info); |
| } |
| |
| QAPI_LIST_PREPEND(fdset_list, fdset_info); |
| } |
| |
| return fdset_list; |
| } |
| |
| AddfdInfo *monitor_fdset_add_fd(int fd, bool has_fdset_id, int64_t fdset_id, |
| const char *opaque, Error **errp) |
| { |
| MonFdset *mon_fdset = NULL; |
| MonFdsetFd *mon_fdset_fd; |
| AddfdInfo *fdinfo; |
| |
| QEMU_LOCK_GUARD(&mon_fdsets_lock); |
| if (has_fdset_id) { |
| QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { |
| /* Break if match found or match impossible due to ordering by ID */ |
| if (fdset_id <= mon_fdset->id) { |
| if (fdset_id < mon_fdset->id) { |
| mon_fdset = NULL; |
| } |
| break; |
| } |
| } |
| } |
| |
| if (mon_fdset == NULL) { |
| int64_t fdset_id_prev = -1; |
| MonFdset *mon_fdset_cur = QLIST_FIRST(&mon_fdsets); |
| |
| if (has_fdset_id) { |
| if (fdset_id < 0) { |
| error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "fdset-id", |
| "a non-negative value"); |
| return NULL; |
| } |
| /* Use specified fdset ID */ |
| QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { |
| mon_fdset_cur = mon_fdset; |
| if (fdset_id < mon_fdset_cur->id) { |
| break; |
| } |
| } |
| } else { |
| /* Use first available fdset ID */ |
| QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { |
| mon_fdset_cur = mon_fdset; |
| if (fdset_id_prev == mon_fdset_cur->id - 1) { |
| fdset_id_prev = mon_fdset_cur->id; |
| continue; |
| } |
| break; |
| } |
| } |
| |
| mon_fdset = g_malloc0(sizeof(*mon_fdset)); |
| if (has_fdset_id) { |
| mon_fdset->id = fdset_id; |
| } else { |
| mon_fdset->id = fdset_id_prev + 1; |
| } |
| |
| /* The fdset list is ordered by fdset ID */ |
| if (!mon_fdset_cur) { |
| QLIST_INSERT_HEAD(&mon_fdsets, mon_fdset, next); |
| } else if (mon_fdset->id < mon_fdset_cur->id) { |
| QLIST_INSERT_BEFORE(mon_fdset_cur, mon_fdset, next); |
| } else { |
| QLIST_INSERT_AFTER(mon_fdset_cur, mon_fdset, next); |
| } |
| } |
| |
| mon_fdset_fd = g_malloc0(sizeof(*mon_fdset_fd)); |
| mon_fdset_fd->fd = fd; |
| mon_fdset_fd->removed = false; |
| mon_fdset_fd->opaque = g_strdup(opaque); |
| QLIST_INSERT_HEAD(&mon_fdset->fds, mon_fdset_fd, next); |
| |
| fdinfo = g_malloc0(sizeof(*fdinfo)); |
| fdinfo->fdset_id = mon_fdset->id; |
| fdinfo->fd = mon_fdset_fd->fd; |
| |
| return fdinfo; |
| } |
| |
| int monitor_fdset_dup_fd_add(int64_t fdset_id, int flags) |
| { |
| #ifdef _WIN32 |
| return -ENOENT; |
| #else |
| MonFdset *mon_fdset; |
| |
| QEMU_LOCK_GUARD(&mon_fdsets_lock); |
| QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { |
| MonFdsetFd *mon_fdset_fd; |
| MonFdsetFd *mon_fdset_fd_dup; |
| int fd = -1; |
| int dup_fd; |
| int mon_fd_flags; |
| |
| if (mon_fdset->id != fdset_id) { |
| continue; |
| } |
| |
| QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) { |
| mon_fd_flags = fcntl(mon_fdset_fd->fd, F_GETFL); |
| if (mon_fd_flags == -1) { |
| return -1; |
| } |
| |
| if ((flags & O_ACCMODE) == (mon_fd_flags & O_ACCMODE)) { |
| fd = mon_fdset_fd->fd; |
| break; |
| } |
| } |
| |
| if (fd == -1) { |
| errno = EACCES; |
| return -1; |
| } |
| |
| dup_fd = qemu_dup_flags(fd, flags); |
| if (dup_fd == -1) { |
| return -1; |
| } |
| |
| mon_fdset_fd_dup = g_malloc0(sizeof(*mon_fdset_fd_dup)); |
| mon_fdset_fd_dup->fd = dup_fd; |
| QLIST_INSERT_HEAD(&mon_fdset->dup_fds, mon_fdset_fd_dup, next); |
| return dup_fd; |
| } |
| |
| errno = ENOENT; |
| return -1; |
| #endif |
| } |
| |
| static int64_t monitor_fdset_dup_fd_find_remove(int dup_fd, bool remove) |
| { |
| MonFdset *mon_fdset; |
| MonFdsetFd *mon_fdset_fd_dup; |
| |
| QEMU_LOCK_GUARD(&mon_fdsets_lock); |
| QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { |
| QLIST_FOREACH(mon_fdset_fd_dup, &mon_fdset->dup_fds, next) { |
| if (mon_fdset_fd_dup->fd == dup_fd) { |
| if (remove) { |
| QLIST_REMOVE(mon_fdset_fd_dup, next); |
| g_free(mon_fdset_fd_dup); |
| if (QLIST_EMPTY(&mon_fdset->dup_fds)) { |
| monitor_fdset_cleanup(mon_fdset); |
| } |
| return -1; |
| } else { |
| return mon_fdset->id; |
| } |
| } |
| } |
| } |
| |
| return -1; |
| } |
| |
| int64_t monitor_fdset_dup_fd_find(int dup_fd) |
| { |
| return monitor_fdset_dup_fd_find_remove(dup_fd, false); |
| } |
| |
| void monitor_fdset_dup_fd_remove(int dup_fd) |
| { |
| monitor_fdset_dup_fd_find_remove(dup_fd, true); |
| } |
| |
| int monitor_fd_param(Monitor *mon, const char *fdname, Error **errp) |
| { |
| int fd; |
| |
| if (!qemu_isdigit(fdname[0]) && mon) { |
| fd = monitor_get_fd(mon, fdname, errp); |
| } else { |
| fd = qemu_parse_fd(fdname); |
| if (fd < 0) { |
| error_setg(errp, "Invalid file descriptor number '%s'", |
| fdname); |
| } |
| } |
| |
| return fd; |
| } |
| |
| static void __attribute__((__constructor__)) monitor_fds_init(void) |
| { |
| qemu_mutex_init(&mon_fdsets_lock); |
| } |