core-admin/vchan/init.c
Marek Marczykowski efbd9466f8 vchan/vm: implement libvchan_cleanup to release resources used by vchan link
In most cases after vchan close program terminates (or reexec itself to wait
for next connection), so cleanup isn't needed (not sure how about evtchn and
shared pages...).
But in case of stubdom it is needed to cleanup before waiting for new
connection - we can't reexec agent there.
2012-12-12 02:39:34 +01:00

461 lines
10 KiB
C

/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef WINNT
#include <sys/types.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <malloc.h>
#include <string.h>
#include <xenctrl.h>
#include <unistd.h>
#ifndef CONFIG_STUBDOM
#include "../u2mfn/u2mfnlib.h"
#else
#include <mm.h>
#endif
#endif
#include <xs.h>
#include <stdio.h>
#include <stdlib.h>
#include "libvchan.h"
static int fill_ctrl(struct libvchan *ctrl, struct vchan_interface *ring, int ring_ref)
{
if (!ctrl || !ring)
return -1;
ctrl->ring = ring;
ctrl->ring_ref = ring_ref;
ring->cons_in = ring->prod_in = ring->cons_out = ring->prod_out =
0;
ring->server_closed = ring->client_closed = 0;
ring->debug = 0xaabbccdd;
return 0;
}
#ifdef QREXEC_RING_V2
static int ring_init(struct libvchan *ctrl)
{
struct gntmem_handle* h;
grant_ref_t grants[1];
int result;
struct vchan_interface *ring;
h = gntmem_open();
if (h == INVALID_HANDLE_VALUE)
return -1;
gntmem_set_local_quota(h, 1);
gntmem_set_global_quota(h, 1);
memset(grants, 0, sizeof(grants));
ring = gntmem_grant_pages_to_domain(h, 0, 1, grants);
if (!ring) {
gntmem_close(h);
return -1;
}
return fill_ctrl(ctrl, ring, grants[0]);
}
#else
static int ring_init(struct libvchan *ctrl)
{
int mfn;
int u2mfn_fd;
struct vchan_interface *ring;
#ifdef CONFIG_STUBDOM
ring = (struct vchan_interface *) memalign(XC_PAGE_SIZE, sizeof(*ring));
if (!ring)
return -1;
mfn = virtual_to_mfn(ring);
#else
u2mfn_fd = u2mfn_get_fd();
if (u2mfn_fd < 0)
return -1;
ring = (struct vchan_interface *) u2mfn_alloc_kpage_with_fd (u2mfn_fd);
if (ring == MAP_FAILED)
return -1;
if (u2mfn_get_last_mfn_with_fd (u2mfn_fd, &mfn) < 0)
return -1;
#endif
return fill_ctrl(ctrl, ring, mfn);
}
#endif
/**
creates event channel;
creates "ring-ref" and "event-channel" xenstore entries;
waits for connection to event channel from the peer
*/
static int server_interface_init(struct libvchan *ctrl, int devno)
{
int ret = -1;
struct xs_handle *xs;
char buf[64];
char ref[16];
#ifdef XENCTRL_HAS_XC_INTERFACE
xc_evtchn *evfd;
#else
EVTCHN evfd;
#endif
evtchn_port_or_error_t port;
#ifdef WINNT
xs = xs_domain_open();
#else
xs = xs_daemon_open();
#endif
if (!xs) {
return ret;
}
#ifdef XENCTRL_HAS_XC_INTERFACE
evfd = xc_evtchn_open(NULL, 0);
if (!evfd)
goto fail;
#else
evfd = xc_evtchn_open();
if (evfd < 0)
goto fail;
#endif
ctrl->evfd = evfd;
// the following hardcoded 0 is the peer domain id
port = xc_evtchn_bind_unbound_port(evfd, 0);
if (port < 0)
goto fail2;
ctrl->evport = port;
ctrl->devno = devno;
#ifdef QREXEC_RING_V2
snprintf(buf, sizeof buf, "device/vchan/%d/version", devno);
if (!xs_write(xs, 0, buf, "2", strlen("2")))
goto fail2;
#endif
snprintf(ref, sizeof ref, "%d", ctrl->ring_ref);
snprintf(buf, sizeof buf, "device/vchan/%d/ring-ref", devno);
if (!xs_write(xs, 0, buf, ref, strlen(ref)))
goto fail2;
snprintf(ref, sizeof ref, "%d", ctrl->evport);
snprintf(buf, sizeof buf, "device/vchan/%d/event-channel", devno);
if (!xs_write(xs, 0, buf, ref, strlen(ref)))
goto fail2;
// do not block in stubdom and windows - libvchan_server_handle_connected will be
// called on first input
#ifndef ASYNC_INIT
// wait for the peer to arrive
if (xc_evtchn_pending(evfd) == -1)
goto fail2;
xc_evtchn_unmask(ctrl->evfd, ctrl->evport);
snprintf(buf, sizeof buf, "device/vchan/%d", devno);
xs_rm(xs, 0, buf);
#endif
ret = 0;
fail2:
if (ret)
xc_evtchn_close(evfd);
fail:
xs_daemon_close(xs);
return ret;
}
#define dir_select(dir1, dir2) \
ctrl->wr_cons = &ctrl->ring->cons_##dir1; \
ctrl->wr_prod = &ctrl->ring->prod_##dir1; \
ctrl->rd_cons = &ctrl->ring->cons_##dir2; \
ctrl->rd_prod = &ctrl->ring->prod_##dir2; \
ctrl->wr_ring = ctrl->ring->buf_##dir1; \
ctrl->rd_ring = ctrl->ring->buf_##dir2; \
ctrl->wr_ring_size = sizeof(ctrl->ring->buf_##dir1); \
ctrl->rd_ring_size = sizeof(ctrl->ring->buf_##dir2)
/**
Run in AppVM (any domain).
Release resources used by vchan link. Should be used after
libvchan_close() to clean connection shutdown, but can be used alone in
recovery case.
\param ctrl connection to cleanup
\returns -1 on failure (errno for details), 0 on success
*/
int libvchan_cleanup(struct libvchan *ctrl)
{
if (!ctrl)
return 0;
if (!ctrl->is_server)
return 0;
/* do not wait flush remaining queue to allow use libvchan_cleanup for
* recovery situation. If someone want clean close, should call
* libvchan_close() first.
*/
#if 0
if (!ctrl->ring->server_closed)
libvchan_close(ctrl);
#endif
if (xc_evtchn_unbind(ctrl->evfd, ctrl->evport) < 0)
return -1;
xc_evtchn_close(ctrl->evfd);
#ifdef QREXEC_RING_V2
/* not implemented yet, need to store gntmem_handle from ring_init somewhere */
assert(0);
/* in case of disabled assertions */
errno = EINVAL;
return -1;
#else /* QREXEC_RING_V2 */
#ifdef CONFIG_STUBDOM
free(ctrl->ring);
#else /* CONFIG_STUBDOM */
munmap(ctrl->ring, 4096);
/* FIXME: leak of u2mfn_fd, check u2mfnlib.c */
#endif /* CONFIG_STUBDOM */
#endif /* QREXEC_RING_V2 */
free(ctrl);
return 0;
}
/**
Run in AppVM (any domain).
Sleeps until the connection is established. (unless in stubdom)
\param devno something like a well-known port.
\returns NULL on failure, handle on success
*/
struct libvchan *libvchan_server_init(int devno)
{
struct libvchan *ctrl =
(struct libvchan *) malloc(sizeof(struct libvchan));
if (!ctrl)
return 0;
if (ring_init(ctrl))
return 0;;
if (server_interface_init(ctrl, devno))
return 0;
/*
We want the same code for read/write functions, regardless whether
we are client, or server. Thus, we do not access buf_in nor buf_out
buffers directly. Instead, in *_init functions, the dir_select
macro assigns proper values to wr* and rd* pointers, so that they
point to correct one out of buf_in or buf_out related fields.
*/
dir_select(in, out);
ctrl->is_server = 1;
return ctrl;
}
int libvchan_server_handle_connected(struct libvchan *ctrl)
{
struct xs_handle *xs;
char buf[64];
int ret = -1;
#ifdef WINNT
xs = xs_domain_open();
#else
xs = xs_daemon_open();
#endif
if (!xs) {
return ret;
}
#ifndef WINNT
// clear the pending flag
xc_evtchn_pending(ctrl->evfd);
#endif
snprintf(buf, sizeof buf, "device/vchan/%d", ctrl->devno);
xs_rm(xs, 0, buf);
ret = 0;
#if 0
fail2:
if (ret)
xc_evtchn_close(ctrl->evfd);
#endif
xs_daemon_close(xs);
return ret;
}
#ifndef WINNT
/**
retrieves ring-ref and event-channel numbers from xenstore (if
they don't exist, return error, because nobody seems to listen);
map the ring, connect the event channel
*/
static int client_interface_init(struct libvchan *ctrl, int domain, int devno)
{
int ret = -1;
unsigned int len;
struct xs_handle *xs;
#ifdef XENCTRL_HAS_XC_INTERFACE
xc_interface *xcfd;
xc_gnttab *xcg;
#else
int xcfd;
int xcg;
#endif
char buf[64];
char *ref;
int version;
#ifdef XENCTRL_HAS_XC_INTERFACE
xc_evtchn *evfd;
#else
int evfd;
#endif
int remote_port;
xs = xs_daemon_open();
if (!xs) {
return ret;
}
version = 1;
snprintf(buf, sizeof buf,
"/local/domain/%d/device/vchan/%d/version", domain,
devno);
ref = xs_read(xs, 0, buf, &len);
if (ref) {
version = atoi(ref);
free(ref);
}
snprintf(buf, sizeof buf,
"/local/domain/%d/device/vchan/%d/ring-ref", domain,
devno);
ref = xs_read(xs, 0, buf, &len);
if (!ref)
goto fail;
ctrl->ring_ref = atoi(ref);
free(ref);
if (!ctrl->ring_ref)
goto fail;
snprintf(buf, sizeof buf,
"/local/domain/%d/device/vchan/%d/event-channel", domain,
devno);
ref = xs_read(xs, 0, buf, &len);
if (!ref)
goto fail;
remote_port = atoi(ref);
free(ref);
if (!remote_port)
goto fail;
switch (version) {
case 1:
#ifdef XENCTRL_HAS_XC_INTERFACE
xcfd = xc_interface_open(NULL, NULL, 0);
if (!xcfd)
goto fail;
#else
xcfd = xc_interface_open();
if (xcfd < 0)
goto fail;
#endif
ctrl->ring = (struct vchan_interface *)
xc_map_foreign_range(xcfd, domain, 4096,
PROT_READ | PROT_WRITE, ctrl->ring_ref);
xc_interface_close(xcfd);
break;
case 2:
xcg = xc_gnttab_open(NULL, 0);
if (xcg < 0)
goto fail;
ctrl->ring = (struct vchan_interface *)
xc_gnttab_map_grant_ref(xcg, domain, ctrl->ring_ref, PROT_READ | PROT_WRITE);
xc_gnttab_close(xcg);
break;
default:
goto fail;
}
if (ctrl->ring == 0 || ctrl->ring == MAP_FAILED)
goto fail;
#ifdef XENCTRL_HAS_XC_INTERFACE
evfd = xc_evtchn_open(NULL, 0);
if (!evfd)
goto fail;
#else
evfd = xc_evtchn_open();
if (evfd < 0)
goto fail;
#endif
ctrl->evfd = evfd;
ctrl->evport =
xc_evtchn_bind_interdomain(evfd, domain, remote_port);
if (ctrl->evport < 0 || xc_evtchn_notify(evfd, ctrl->evport))
xc_evtchn_close(evfd);
else
ret = 0;
fail:
xs_daemon_close(xs);
return ret;
}
/**
Run on the client side of connection (currently, must be dom0).
\returns NULL on failure (e.g. noone listening), handle on success
*/
struct libvchan *libvchan_client_init(int domain, int devno)
{
struct libvchan *ctrl =
(struct libvchan *) malloc(sizeof(struct libvchan));
if (!ctrl)
return 0;
if (client_interface_init(ctrl, domain, devno))
return 0;
// See comment in libvchan_server_init
dir_select(out, in);
ctrl->is_server = 0;
return ctrl;
}
#else
// Windows domains can not be dom0
struct libvchan *libvchan_client_init(int domain, int devno)
{
return NULL;
}
#endif