sd-varlink: add async server-side upgrade API
This commit is contained in:
@@ -977,6 +977,14 @@ manpages = [
|
||||
''],
|
||||
['sd_varlink_is_connected', '3', ['sd_varlink_is_idle'], ''],
|
||||
['sd_varlink_push_fd', '3', ['sd_varlink_push_dup_fd'], ''],
|
||||
['sd_varlink_reply_and_upgrade',
|
||||
'3',
|
||||
['sd_varlink_bind_upgrade',
|
||||
'sd_varlink_respond_and_upgrade',
|
||||
'sd_varlink_respond_and_upgradeb',
|
||||
'sd_varlink_respond_and_upgradebo',
|
||||
'sd_varlink_upgrade_t'],
|
||||
''],
|
||||
['sd_varlink_send', '3', ['sd_varlink_sendb', 'sd_varlink_sendbo'], ''],
|
||||
['sd_varlink_server_listen_address',
|
||||
'3',
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="sd_varlink_reply_and_upgrade" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>sd_varlink_reply_and_upgrade</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>sd_varlink_reply_and_upgrade</refentrytitle>
|
||||
<manvolnum>3</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>sd_varlink_reply_and_upgrade</refname>
|
||||
<refname>sd_varlink_respond_and_upgrade</refname>
|
||||
<refname>sd_varlink_respond_and_upgradeb</refname>
|
||||
<refname>sd_varlink_respond_and_upgradebo</refname>
|
||||
<refname>sd_varlink_bind_upgrade</refname>
|
||||
<refname>sd_varlink_upgrade_t</refname>
|
||||
|
||||
<refpurpose>Reply to a Varlink method call and take over the connection for a raw protocol</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<funcsynopsis>
|
||||
<funcsynopsisinfo>#include <systemd/sd-varlink.h></funcsynopsisinfo>
|
||||
|
||||
<funcsynopsisinfo>typedef int (*<function>sd_varlink_upgrade_t</function>)(sd_varlink *<parameter>link</parameter>, int <parameter>input_fd</parameter>, int <parameter>output_fd</parameter>, void *<parameter>userdata</parameter>);</funcsynopsisinfo>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>int <function>sd_varlink_reply_and_upgrade</function></funcdef>
|
||||
<paramdef>sd_varlink *<parameter>link</parameter></paramdef>
|
||||
<paramdef>sd_json_variant *<parameter>parameters</parameter></paramdef>
|
||||
<paramdef>int *<parameter>ret_input_fd</parameter></paramdef>
|
||||
<paramdef>int *<parameter>ret_output_fd</parameter></paramdef>
|
||||
</funcprototype>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>int <function>sd_varlink_respond_and_upgrade</function></funcdef>
|
||||
<paramdef>sd_varlink *<parameter>link</parameter></paramdef>
|
||||
<paramdef>sd_json_variant *<parameter>parameters</parameter></paramdef>
|
||||
</funcprototype>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>int <function>sd_varlink_respond_and_upgradeb</function></funcdef>
|
||||
<paramdef>sd_varlink *<parameter>link</parameter></paramdef>
|
||||
<paramdef>…</paramdef>
|
||||
</funcprototype>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>int <function>sd_varlink_respond_and_upgradebo</function></funcdef>
|
||||
<paramdef>sd_varlink *<parameter>link</parameter></paramdef>
|
||||
<paramdef>…</paramdef>
|
||||
</funcprototype>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>int <function>sd_varlink_bind_upgrade</function></funcdef>
|
||||
<paramdef>sd_varlink *<parameter>link</parameter></paramdef>
|
||||
<paramdef>sd_varlink_upgrade_t <parameter>callback</parameter></paramdef>
|
||||
</funcprototype>
|
||||
</funcsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para>These functions implement the server side of a Varlink <emphasis>protocol upgrade</emphasis>. A
|
||||
Varlink client may request an upgrade when invoking a method (see
|
||||
<citerefentry><refentrytitle>sd_varlink_call</refentrytitle><manvolnum>3</manvolnum></citerefentry> and
|
||||
the <constant>SD_VARLINK_METHOD_UPGRADE</constant> flag passed to method callbacks). If a method is
|
||||
invoked this way, the server may send one final reply and then abandon the Varlink protocol on the
|
||||
connection entirely, handing the underlying socket or pipe file descriptors back to the caller so that an
|
||||
arbitrary, non-Varlink ("raw") protocol may be spoken over them instead. This is useful for methods that
|
||||
negotiate a switch to a streaming or binary protocol, in a manner similar to the
|
||||
<literal>Upgrade:</literal> mechanism of HTTP.</para>
|
||||
|
||||
<para><function>sd_varlink_reply_and_upgrade()</function> sends a final reply to the currently processed
|
||||
method call and then takes over the connection's file descriptors <emphasis>synchronously</emphasis>. It
|
||||
takes the Varlink connection object, a JSON object with the reply parameters (which may be
|
||||
<constant>NULL</constant>), and two output pointers for the file descriptors. The <parameter>parameters</parameter>
|
||||
object is validated against the method's declared reply signature, if known. The reply is flushed to the
|
||||
connection before the file descriptors are returned, so that the client has received the full reply before
|
||||
the caller starts speaking the upgraded protocol. On success the connection is detached from its event
|
||||
loop (if any) and disconnected, and ownership of the file descriptors is transferred to the caller, which
|
||||
is responsible for eventually closing them with
|
||||
<citerefentry project='man-pages'><refentrytitle>close</refentrytitle><manvolnum>2</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>The returned file descriptors are switched to blocking mode. For bidirectional sockets a single
|
||||
underlying file descriptor carries both directions; in this case <parameter>ret_input_fd</parameter> and
|
||||
<parameter>ret_output_fd</parameter> are returned as two independent (duplicated) descriptors referring
|
||||
to the same socket, so they may be closed separately. For transports backed by a pair of pipes the two
|
||||
descriptors differ and refer to the distinct read and write ends. Either
|
||||
<parameter>ret_input_fd</parameter> or <parameter>ret_output_fd</parameter> (but not both) may be
|
||||
<constant>NULL</constant>; in that case the unwanted direction is shut down (if they refer to the same
|
||||
socket, see <citerefentry
|
||||
project='man-pages'><refentrytitle>shutdown</refentrytitle><manvolnum>2</manvolnum></citerefentry>) or
|
||||
closed (if separate file descriptors) rather than returned.</para>
|
||||
|
||||
<para>Note that <function>sd_varlink_reply_and_upgrade()</function> blocks synchronously until the reply
|
||||
has been flushed to the socket. A misbehaving or adversarial client that stops reading could stall the
|
||||
caller. It should hence not be used in servers that multiplex many Varlink connections in a single event
|
||||
loop, as one stalled connection would block progress on all others. Use
|
||||
<function>sd_varlink_respond_and_upgrade()</function> in that case instead.</para>
|
||||
|
||||
<para><function>sd_varlink_respond_and_upgrade()</function> is the <emphasis>asynchronous</emphasis>
|
||||
counterpart of <function>sd_varlink_reply_and_upgrade()</function>. It enqueues the final reply and
|
||||
returns immediately, without blocking and without returning the file descriptors directly. The reply is
|
||||
subsequently flushed by the event loop during regular
|
||||
<citerefentry><refentrytitle>sd_varlink_process</refentrytitle><manvolnum>3</manvolnum></citerefentry>
|
||||
invocations. Once the reply has been fully flushed, the connection's file descriptors are handed to the
|
||||
upgrade callback previously registered with <function>sd_varlink_bind_upgrade()</function>, and the
|
||||
Varlink connection is disconnected. This variant is the right choice for servers that handle many
|
||||
connections concurrently, as it never blocks on a single client.</para>
|
||||
|
||||
<para><function>sd_varlink_respond_and_upgradeb()</function> is similar to
|
||||
<function>sd_varlink_respond_and_upgrade()</function>, but instead of expecting a fully constructed
|
||||
<type>sd_json_variant</type> object carrying the reply parameters, this object is constructed on-the-fly
|
||||
from the variadic argument list, in a style identical to
|
||||
<citerefentry><refentrytitle>sd_json_build</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
|
||||
<function>sd_varlink_respond_and_upgradebo()</function> is identical to
|
||||
<function>sd_varlink_respond_and_upgradeb()</function>, but an enclosing JSON object is added implicitly,
|
||||
so that the argument list is expected to consist of object field pairs only, in a style identical to
|
||||
<citerefentry><refentrytitle>sd_json_buildo</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para><function>sd_varlink_bind_upgrade()</function> registers the upgrade
|
||||
<parameter>callback</parameter> that is invoked once a reply enqueued via
|
||||
<function>sd_varlink_respond_and_upgrade()</function> (or one of its variants) has been flushed to the
|
||||
connection. The callback is of type <type>sd_varlink_upgrade_t</type> and receives the Varlink connection
|
||||
object, the input and output file descriptors of the (now upgraded) connection, and the userdata pointer
|
||||
associated with the connection (see
|
||||
<citerefentry><refentrytitle>sd_varlink_set_userdata</refentrytitle><manvolnum>3</manvolnum></citerefentry>).
|
||||
Ownership of the two file descriptors is transferred to the callback (regardless if the callback later
|
||||
fails or not). The descriptor semantics (blocking mode, duplication for bidirectional sockets, distinct
|
||||
ends for pipe pairs) match those described for <function>sd_varlink_reply_and_upgrade()</function>
|
||||
above. Passing <constant>NULL</constant> as <parameter>callback</parameter> removes a previously
|
||||
registered callback.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Return Value</title>
|
||||
|
||||
<para>On success, <function>sd_varlink_reply_and_upgrade()</function>,
|
||||
<function>sd_varlink_respond_and_upgrade()</function>,
|
||||
<function>sd_varlink_respond_and_upgradeb()</function> and
|
||||
<function>sd_varlink_respond_and_upgradebo()</function> return a positive integer.
|
||||
<function>sd_varlink_bind_upgrade()</function> returns a non-negative integer on success. On failure, they
|
||||
all return a negative errno-style error code.</para>
|
||||
|
||||
<refsect2>
|
||||
<title>Errors</title>
|
||||
|
||||
<para>Returned errors may indicate the following problems:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><constant>-EINVAL</constant></term>
|
||||
|
||||
<listitem><para>An argument is invalid.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-ENOTCONN</constant></term>
|
||||
|
||||
<listitem><para>The Varlink connection object is not connected.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-EBUSY</constant></term>
|
||||
|
||||
<listitem><para>The connection is not currently processing a method call, so there is no reply to
|
||||
send. When returned by <function>sd_varlink_bind_upgrade()</function>, a different upgrade callback
|
||||
was already registered on the connection.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-EPROTO</constant></term>
|
||||
|
||||
<listitem><para>The currently processed method call did not request a protocol upgrade, hence the
|
||||
connection may not be taken over.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-EBADMSG</constant></term>
|
||||
|
||||
<listitem><para>The client sent unexpected data before the upgrade completed, i.e. it pipelined
|
||||
raw protocol data before receiving the upgrade reply.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>-ENOMEM</constant></term>
|
||||
|
||||
<listitem><para>Memory allocation failed.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
<xi:include href="libsystemd-pkgconfig.xml" />
|
||||
|
||||
<refsect1>
|
||||
<title>History</title>
|
||||
<para><function>sd_varlink_reply_and_upgrade()</function> was added in version 261.</para>
|
||||
<para><function>sd_varlink_respond_and_upgrade()</function>,
|
||||
<function>sd_varlink_respond_and_upgradeb()</function>,
|
||||
<function>sd_varlink_respond_and_upgradebo()</function> and
|
||||
<function>sd_varlink_bind_upgrade()</function> were added in version 262.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sd-varlink</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sd_varlink_reply</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sd_varlink_call</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sd_varlink_process</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sd_varlink_set_userdata</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>sd_json_build</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
@@ -1104,3 +1104,10 @@ global:
|
||||
sd_varlink_reply_and_upgrade;
|
||||
sd_varlink_set_sentinel;
|
||||
} LIBSYSTEMD_260;
|
||||
|
||||
LIBSYSTEMD_262 {
|
||||
global:
|
||||
sd_varlink_bind_upgrade;
|
||||
sd_varlink_respond_and_upgrade;
|
||||
sd_varlink_respond_and_upgradeb;
|
||||
} LIBSYSTEMD_261;
|
||||
|
||||
@@ -62,9 +62,11 @@ static const char* const varlink_state_table[_VARLINK_STATE_MAX] = {
|
||||
[VARLINK_PROCESSING_METHOD_ONEWAY] = "processing-method-oneway",
|
||||
[VARLINK_PROCESSING_METHOD_UPGRADE] = "processing-method-upgrade",
|
||||
[VARLINK_PROCESSED_METHOD] = "processed-method",
|
||||
[VARLINK_PROCESSED_METHOD_UPGRADE] = "processed-method-upgrade",
|
||||
[VARLINK_PENDING_METHOD] = "pending-method",
|
||||
[VARLINK_PENDING_METHOD_MORE] = "pending-method-more",
|
||||
[VARLINK_PENDING_METHOD_UPGRADE] = "pending-method-upgrade",
|
||||
[VARLINK_UPGRADING] = "upgrading",
|
||||
[VARLINK_PENDING_DISCONNECT] = "pending-disconnect",
|
||||
[VARLINK_PENDING_TIMEOUT] = "pending-timeout",
|
||||
[VARLINK_PROCESSING_DISCONNECT] = "processing-disconnect",
|
||||
@@ -1407,6 +1409,11 @@ static int varlink_dispatch_method(sd_varlink *v) {
|
||||
varlink_set_state(v, VARLINK_PENDING_METHOD_UPGRADE);
|
||||
break;
|
||||
|
||||
case VARLINK_PROCESSED_METHOD_UPGRADE: /* Upgrade requested, let's now wait till everything is flushed */
|
||||
varlink_clear_current(v);
|
||||
varlink_set_state(v, VARLINK_UPGRADING);
|
||||
break;
|
||||
|
||||
case VARLINK_DISCONNECTED: /* Handler called sd_varlink_close() on us, which is fine */
|
||||
break;
|
||||
|
||||
@@ -1474,6 +1481,41 @@ static int varlink_handle_upgrade_fds(sd_varlink *v, int *ret_input_fd, int *ret
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int varlink_dispatch_upgrade(sd_varlink *v) {
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
|
||||
if (v->state != VARLINK_UPGRADING)
|
||||
return 0;
|
||||
|
||||
r = json_stream_has_buffered_output(&v->stream);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
return 0;
|
||||
|
||||
/* Detach from the event loop before stealing the fds */
|
||||
sd_varlink_detach_event(v);
|
||||
|
||||
_cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF;
|
||||
r = varlink_handle_upgrade_fds(v, &input_fd, &output_fd);
|
||||
sd_varlink_close(v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Fail if by this time the caller still hasn't set an upgrade callback. */
|
||||
if (!v->upgrade_callback)
|
||||
return -ENOPKG;
|
||||
|
||||
/* No matter what, we donate the fds to callback */
|
||||
r = v->upgrade_callback(v, TAKE_FD(input_fd), TAKE_FD(output_fd), v->userdata);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
_public_ int sd_varlink_process(sd_varlink *v) {
|
||||
int r;
|
||||
|
||||
@@ -1490,6 +1532,12 @@ _public_ int sd_varlink_process(sd_varlink *v) {
|
||||
if (r != 0)
|
||||
goto finish;
|
||||
|
||||
r = varlink_dispatch_upgrade(v);
|
||||
if (r < 0)
|
||||
varlink_log_errno(v, r, "Upgrade dispatch failed: %m");
|
||||
if (r != 0)
|
||||
goto finish;
|
||||
|
||||
r = varlink_dispatch_reply(v);
|
||||
if (r < 0)
|
||||
varlink_log_errno(v, r, "Reply dispatch failed: %m");
|
||||
@@ -2457,11 +2505,12 @@ _public_ int sd_varlink_replyb(sd_varlink *v, ...) {
|
||||
return sd_varlink_reply(v, parameters);
|
||||
}
|
||||
|
||||
_public_ int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parameters, int *ret_input_fd, int *ret_output_fd) {
|
||||
_public_ int sd_varlink_respond_and_upgrade(
|
||||
sd_varlink *v,
|
||||
sd_json_variant *parameters) {
|
||||
int r;
|
||||
|
||||
assert_return(v, -EINVAL);
|
||||
assert_return(ret_input_fd || ret_output_fd, -EINVAL);
|
||||
|
||||
if (v->state == VARLINK_DISCONNECTED)
|
||||
return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected.");
|
||||
@@ -2508,6 +2557,55 @@ _public_ int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parame
|
||||
if (r < 0)
|
||||
return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
|
||||
|
||||
switch (v->state) {
|
||||
|
||||
case VARLINK_PENDING_METHOD_UPGRADE:
|
||||
varlink_clear_current(v);
|
||||
varlink_set_state(v, VARLINK_UPGRADING);
|
||||
break;
|
||||
|
||||
case VARLINK_PROCESSING_METHOD_UPGRADE:
|
||||
varlink_set_state(v, VARLINK_PROCESSED_METHOD_UPGRADE);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
_public_ int sd_varlink_respond_and_upgradeb(sd_varlink *v, ...) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL;
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
assert_return(v, -EINVAL);
|
||||
|
||||
va_start(ap, v);
|
||||
r = sd_json_buildv(¶meters, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_varlink_respond_and_upgrade(v, parameters);
|
||||
}
|
||||
|
||||
_public_ int sd_varlink_reply_and_upgrade(
|
||||
sd_varlink *v,
|
||||
sd_json_variant *parameters,
|
||||
int *ret_input_fd,
|
||||
int *ret_output_fd) {
|
||||
int r;
|
||||
|
||||
assert_return(v, -EINVAL);
|
||||
assert_return(ret_input_fd || ret_output_fd, -EINVAL);
|
||||
|
||||
r = sd_varlink_respond_and_upgrade(v, parameters);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Flush the reply to the socket before stealing the fds. The reply must be fully written
|
||||
* before the caller starts speaking the upgraded protocol. */
|
||||
r = json_stream_flush(&v->stream);
|
||||
@@ -2797,6 +2895,17 @@ _public_ int sd_varlink_bind_reply(sd_varlink *v, sd_varlink_reply_t reply) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_public_ int sd_varlink_bind_upgrade(sd_varlink *v, sd_varlink_upgrade_t upgrade) {
|
||||
assert_return(v, -EINVAL);
|
||||
|
||||
if (upgrade && v->upgrade_callback && upgrade != v->upgrade_callback)
|
||||
return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "A different callback was already set.");
|
||||
|
||||
v->upgrade_callback = upgrade;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
_public_ void* sd_varlink_set_userdata(sd_varlink *v, void *userdata) {
|
||||
void *old;
|
||||
|
||||
|
||||
@@ -1177,6 +1177,127 @@ TEST(upgrade_pipelining) {
|
||||
ASSERT_OK(sd_future_result(f));
|
||||
}
|
||||
|
||||
/* Stashes the fds handed to the asynchronous upgrade callback so the raw-I/O fiber can pick them up.
|
||||
* A single connection is upgraded per test run, so globals are sufficient. */
|
||||
static int respond_upgrade_input_fd = -EBADF;
|
||||
static int respond_upgrade_output_fd = -EBADF;
|
||||
|
||||
static int respond_upgrade_io_fiber(void *arg) {
|
||||
_cleanup_close_ int input_fd = TAKE_FD(respond_upgrade_input_fd), output_fd = TAKE_FD(respond_upgrade_output_fd);
|
||||
|
||||
/* Same reverse-echo raw protocol as method_upgrade(), but reached through the asynchronous
|
||||
* upgrade callback instead of a synchronous fiber method: read until the client shuts down its
|
||||
* write side, reverse the bytes, and write them back. */
|
||||
char buf[64] = {};
|
||||
ssize_t n = ASSERT_OK(loop_read(input_fd, buf, sizeof(buf) - 1, /* do_poll= */ true));
|
||||
ASSERT_GT(n, 0);
|
||||
|
||||
for (ssize_t i = 0; i < n / 2; i++)
|
||||
SWAP_TWO(buf[i], buf[n - 1 - i]);
|
||||
|
||||
ASSERT_OK(loop_write(output_fd, buf, n));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int respond_upgrade_callback(sd_varlink *link, int input_fd, int output_fd, void *userdata) {
|
||||
sd_event *e = ASSERT_PTR(userdata);
|
||||
|
||||
/* The upgrade callback is invoked from sd_varlink_process() in the main event loop (not a
|
||||
* fiber), so it must not block on raw I/O. Hand the connection's fds to a floating fiber that
|
||||
* speaks the raw protocol, allowing the client fiber to make progress concurrently. Ownership of
|
||||
* the fds is transferred to us regardless of what happens next, so stash them before spawning. */
|
||||
respond_upgrade_input_fd = input_fd;
|
||||
respond_upgrade_output_fd = output_fd;
|
||||
|
||||
ASSERT_OK(sd_fiber_new(e, "respond-upgrade-io", respond_upgrade_io_fiber, /* userdata= */ NULL, /* destroy= */ NULL, /* ret= */ NULL));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int method_respond_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
int r;
|
||||
|
||||
ASSERT_TRUE(FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE));
|
||||
|
||||
/* Register the async upgrade callback, then enqueue the final reply. Unlike
|
||||
* sd_varlink_reply_and_upgrade(), sd_varlink_respond_and_upgradebo() returns immediately without
|
||||
* blocking: the reply is flushed by the event loop and the fds are subsequently handed to
|
||||
* respond_upgrade_callback(). This is why the method may be bound as a regular (non-fiber) method. */
|
||||
ASSERT_OK(sd_varlink_bind_upgrade(link, respond_upgrade_callback));
|
||||
|
||||
r = sd_varlink_respond_and_upgradebo(
|
||||
link,
|
||||
SD_JSON_BUILD_PAIR_STRING("greeting", "aloha"));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int respond_upgrade_client_fiber(void *arg) {
|
||||
_cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *c = NULL;
|
||||
_cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF;
|
||||
sd_json_variant *o = NULL;
|
||||
const char *error_id = NULL;
|
||||
|
||||
ASSERT_OK(sd_varlink_connect_address(&c, arg));
|
||||
ASSERT_OK(sd_varlink_set_description(c, "respond-upgrade-client"));
|
||||
|
||||
ASSERT_OK(sd_varlink_call_and_upgrade(c, "io.test.RespondUpgrade", /* parameters= */ NULL, &o, &error_id, &input_fd, &output_fd));
|
||||
ASSERT_NULL(error_id);
|
||||
|
||||
/* The reply parameters enqueued asynchronously by the server must have arrived intact. */
|
||||
ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(o, "greeting")), "aloha");
|
||||
|
||||
ASSERT_GE(input_fd, 0);
|
||||
ASSERT_GE(output_fd, 0);
|
||||
ASSERT_NE(input_fd, output_fd); /* library dups for bidirectional sockets */
|
||||
|
||||
/* Send a test string, shut down write side so the server sees EOF, then read the reversed reply */
|
||||
static const char msg[] = "Hello!";
|
||||
ASSERT_OK(loop_write(output_fd, msg, strlen(msg)));
|
||||
ASSERT_OK_ERRNO(shutdown(output_fd, SHUT_WR));
|
||||
|
||||
char buf[64] = {};
|
||||
ssize_t n = ASSERT_OK(loop_read(input_fd, buf, strlen(msg), /* do_poll= */ true));
|
||||
ASSERT_EQ((size_t) n, strlen(msg));
|
||||
ASSERT_STREQ(buf, "!olleH");
|
||||
|
||||
ASSERT_OK(sd_event_exit(sd_fiber_get_event(), EXIT_SUCCESS));
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST(respond_upgrade) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
|
||||
_cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
|
||||
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
|
||||
_cleanup_(sd_future_unrefp) sd_future *f = NULL;
|
||||
const char *sp;
|
||||
|
||||
ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir));
|
||||
sp = strjoina(tmpdir, "/socket");
|
||||
|
||||
ASSERT_OK(sd_event_new(&e));
|
||||
|
||||
ASSERT_OK(sd_varlink_server_new(&s, SD_VARLINK_SERVER_UPGRADABLE|SD_VARLINK_SERVER_INHERIT_USERDATA));
|
||||
ASSERT_OK(sd_varlink_server_set_description(s, "respond-upgrade-server"));
|
||||
/* The event loop is propagated to the async upgrade callback via the connection userdata (inherited
|
||||
* from the server) so the callback can spawn the raw-I/O fiber. */
|
||||
sd_varlink_server_set_userdata(s, e);
|
||||
/* Unlike TEST(upgrade), this is a *regular* (non-fiber) method: it drives the asynchronous
|
||||
* sd_varlink_respond_and_upgradebo() API, which never blocks the event loop on a single client. */
|
||||
ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.RespondUpgrade", method_respond_upgrade));
|
||||
ASSERT_OK(sd_varlink_server_listen_address(s, sp, 0600));
|
||||
ASSERT_OK(sd_varlink_server_attach_event(s, e, 0));
|
||||
|
||||
ASSERT_OK(sd_fiber_new(e, "respond-upgrade-client", respond_upgrade_client_fiber, (void*) sp, /* destroy= */ NULL, &f));
|
||||
|
||||
ASSERT_OK(sd_event_loop(e));
|
||||
|
||||
ASSERT_OK(sd_future_result(f));
|
||||
}
|
||||
|
||||
typedef struct ExecDirServer {
|
||||
sd_varlink_server *server;
|
||||
const char *name;
|
||||
|
||||
@@ -28,9 +28,11 @@ typedef enum VarlinkState {
|
||||
VARLINK_PROCESSING_METHOD_ONEWAY,
|
||||
VARLINK_PROCESSING_METHOD_UPGRADE,
|
||||
VARLINK_PROCESSED_METHOD,
|
||||
VARLINK_PROCESSED_METHOD_UPGRADE,
|
||||
VARLINK_PENDING_METHOD,
|
||||
VARLINK_PENDING_METHOD_MORE,
|
||||
VARLINK_PENDING_METHOD_UPGRADE,
|
||||
VARLINK_UPGRADING,
|
||||
|
||||
/* Common states (only during shutdown) */
|
||||
VARLINK_PENDING_DISCONNECT,
|
||||
@@ -64,9 +66,11 @@ typedef enum VarlinkState {
|
||||
VARLINK_PROCESSING_METHOD_ONEWAY, \
|
||||
VARLINK_PROCESSING_METHOD_UPGRADE, \
|
||||
VARLINK_PROCESSED_METHOD, \
|
||||
VARLINK_PROCESSED_METHOD_UPGRADE, \
|
||||
VARLINK_PENDING_METHOD, \
|
||||
VARLINK_PENDING_METHOD_MORE, \
|
||||
VARLINK_PENDING_METHOD_UPGRADE)
|
||||
VARLINK_PENDING_METHOD_UPGRADE, \
|
||||
VARLINK_UPGRADING)
|
||||
|
||||
/* Tests whether we are expected to generate a method call reply, i.e. are processing a method call, except
|
||||
* one with the ONEWAY flag set. */
|
||||
@@ -92,6 +96,7 @@ typedef struct sd_varlink {
|
||||
unsigned n_pending;
|
||||
|
||||
sd_varlink_reply_t reply_callback;
|
||||
sd_varlink_upgrade_t upgrade_callback;
|
||||
|
||||
sd_json_variant *current;
|
||||
sd_json_variant *current_collected;
|
||||
|
||||
@@ -84,6 +84,7 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_invocation_flags_t) {
|
||||
|
||||
typedef int (*sd_varlink_method_t)(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
|
||||
typedef int (*sd_varlink_reply_t)(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata);
|
||||
typedef int (*sd_varlink_upgrade_t)(sd_varlink *link, int input_fd, int output_fd, void *userdata);
|
||||
typedef int (*sd_varlink_connect_t)(sd_varlink_server *server, sd_varlink *link, void *userdata);
|
||||
typedef void (*sd_varlink_disconnect_t)(sd_varlink_server *server, sd_varlink *link, void *userdata);
|
||||
|
||||
@@ -182,6 +183,12 @@ int sd_varlink_replyb(sd_varlink *v, ...);
|
||||
* connections. */
|
||||
int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parameters, int *ret_input_fd, int *ret_output_fd);
|
||||
|
||||
/* Like sd_varlink_reply_and_upgrade() but asynchronous */
|
||||
int sd_varlink_respond_and_upgrade(sd_varlink *v, sd_json_variant *parameters);
|
||||
int sd_varlink_respond_and_upgradeb(sd_varlink *v, ...);
|
||||
#define sd_varlink_respond_and_upgradebo(v, ...) \
|
||||
sd_varlink_respond_and_upgradeb((v), SD_JSON_BUILD_OBJECT(__VA_ARGS__))
|
||||
|
||||
/* Enqueue a (final) error */
|
||||
int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_variant *parameters);
|
||||
int sd_varlink_errorb(sd_varlink *v, const char *error_id, ...);
|
||||
@@ -224,6 +231,9 @@ int sd_varlink_set_allow_fd_passing_output(sd_varlink *v, int b);
|
||||
/* Bind a disconnect, reply or timeout callback */
|
||||
int sd_varlink_bind_reply(sd_varlink *v, sd_varlink_reply_t reply);
|
||||
|
||||
/* Bind an upgrade callback (server side) */
|
||||
int sd_varlink_bind_upgrade(sd_varlink *v, sd_varlink_upgrade_t upgrade);
|
||||
|
||||
void* sd_varlink_set_userdata(sd_varlink *v, void *userdata);
|
||||
void* sd_varlink_get_userdata(sd_varlink *v);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user