mirror of
				https://github.com/qemu/qemu.git
				synced 2025-10-31 04:06:46 +00:00 
			
		
		
		
	 7ef88b5334
			
		
	
	
		7ef88b5334
		
	
	
	
	
		
			
			Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20230124121946.1139465-27-armbru@redhat.com>
		
			
				
	
	
		
			469 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			469 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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);
 | |
| }
 |