]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #12520 from ssahani/geneve
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 10 May 2019 17:47:19 +0000 (19:47 +0200)
committerGitHub <noreply@github.com>
Fri, 10 May 2019 17:47:19 +0000 (19:47 +0200)
networkd: Geneve add DF feature and allow TTL to bechosen by kernel

65 files changed:
TODO
coccinelle/sd_event_source_disable_unref.cocci [new file with mode: 0644]
man/journalctl.xml
man/rules/meson.build
man/sd_event_source_unref.xml
man/systemd.network.xml
src/core/service.c
src/fuzz/fuzz-varlink.c [new file with mode: 0644]
src/fuzz/meson.build
src/import/curl-util.c
src/journal/journal-file.c
src/journal/journalctl.c
src/journal/journald-server.c
src/journal/journald-server.h
src/libsystemd-network/sd-dhcp-client.c
src/libsystemd-network/test-dhcp-client.c
src/libsystemd/libsystemd.sym
src/libsystemd/sd-event/sd-event.c
src/network/netdev/bond.h
src/network/netdev/fou-tunnel.c
src/network/netdev/fou-tunnel.h
src/network/netdev/ipvlan.h
src/network/netdev/l2tp-tunnel.c
src/network/netdev/l2tp-tunnel.h
src/network/netdev/macsec.c
src/network/netdev/macsec.h
src/network/netdev/tunnel.c
src/network/netdev/tuntap.c
src/network/netdev/vcan.h
src/network/netdev/veth.c
src/network/netdev/vlan.c
src/network/netdev/wireguard.h
src/network/networkd-dhcp4.c
src/network/networkd-dhcp6.c
src/network/networkd-ipv4ll.c
src/network/networkd-ipv6-proxy-ndp.c
src/network/networkd-link.c
src/network/networkd-manager.c
src/network/networkd-manager.h
src/network/networkd-ndisc.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/network/networkd-routing-policy-rule.h
src/network/networkd.c
src/network/test-network.c
src/shared/meson.build
src/shared/varlink.c [new file with mode: 0644]
src/shared/varlink.h [new file with mode: 0644]
src/systemd/sd-dhcp-client.h
src/systemd/sd-event.h
src/systemd/sd-netlink.h
src/test/meson.build
src/test/test-alloc-util.c
src/test/test-varlink.c [new file with mode: 0644]
test/fuzz/fuzz-network-parser/directives.network
test/fuzz/fuzz-varlink/array [new file with mode: 0644]
test/fuzz/fuzz-varlink/do-something [new file with mode: 0644]
test/fuzz/fuzz-varlink/huge-method [new file with mode: 0644]
test/fuzz/fuzz-varlink/method-call [new file with mode: 0644]
test/fuzz/fuzz-varlink/method-error [new file with mode: 0644]
test/fuzz/fuzz-varlink/method-reply [new file with mode: 0644]
test/fuzz/fuzz-varlink/timeout-d8a88bf4adea54537d21e3afb396e1a55c5b58bf [new file with mode: 0644]
test/test-functions
units/systemd-journal-flush.service.in

diff --git a/TODO b/TODO
index 4577c93612a5c160c277315dd765bd25322efe2e..d87a70a0b5327a5cd6a78a1e44261103cc712def 100644 (file)
--- a/TODO
+++ b/TODO
@@ -19,6 +19,9 @@ Janitorial Clean-ups:
 
 Features:
 
+* when killing due to service watchdog timeout maybe detect whether target
+  process is under ptracing and then log loudly and continue instead.
+
 * tweak journald context caching. In addition to caching per-process attributes
   keyed by PID, cache per-cgroup attributes (i.e. the various xattrs we read)
   keyed by cgroup path, and guarded by ctime changes. This should provide us
diff --git a/coccinelle/sd_event_source_disable_unref.cocci b/coccinelle/sd_event_source_disable_unref.cocci
new file mode 100644 (file)
index 0000000..2763fef
--- /dev/null
@@ -0,0 +1,36 @@
+@@
+expression p;
+@@
+- if (p) {
+-         (void) sd_event_source_set_enabled(p, SD_EVENT_OFF);
+-         p = sd_event_source_unref(p);
+- }
++ p = sd_event_source_disable_unref(p);
+@@
+expression p;
+@@
+- if (p) {
+-         sd_event_source_set_enabled(p, SD_EVENT_OFF);
+-         sd_event_source_unref(p);
+- }
++ sd_event_source_disable_unref(p);
+@@
+expression p;
+@@
+- if (p) {
+-         (void) sd_event_source_set_enabled(p, SD_EVENT_OFF);
+-         sd_event_source_unref(p);
+- }
++ sd_event_source_disable_unref(p);
+@@
+expression p;
+@@
+- (void) sd_event_source_set_enabled(p, SD_EVENT_OFF);
+- sd_event_source_unref(p);
++ sd_event_source_disable_unref(p);
+@@
+expression p;
+@@
+- sd_event_source_set_enabled(p, SD_EVENT_OFF);
+- sd_event_source_unref(p);
++ sd_event_source_disable_unref(p);
index a3c67f5e82d1335fced6908f36d6209eaf2fdbf9..0ecab521fa0aae8f18f6e16db126ed7e1a217465 100644 (file)
       <varlistentry>
         <term><option>--flush</option></term>
 
-        <listitem><para>Asks the journal daemon to flush any log data
-        stored in <filename>/run/log/journal</filename> into
-        <filename>/var/log/journal</filename>, if persistent storage
-        is enabled. This call does not return until the operation is
-        complete. Note that this call is idempotent: the data is only
-        flushed from <filename>/run/log/journal</filename> into
-        <filename>/var/log/journal</filename> once during system
-        runtime, and this command exits cleanly without executing any
-        operation if this has already happened. This command
-        effectively guarantees that all data is flushed to
-        <filename>/var/log/journal</filename> at the time it
-        returns.</para></listitem>
+        <listitem><para>Asks the journal daemon to flush any log data stored in
+        <filename>/run/log/journal/</filename> into <filename>/var/log/journal/</filename>, if persistent
+        storage is enabled. This call does not return until the operation is complete. Note that this call is
+        idempotent: the data is only flushed from <filename>/run/log/journal/</filename> into
+        <filename>/var/log/journal</filename> once during system runtime (but see
+        <option>--relinquish-var</option> below), and this command exits cleanly without executing any
+        operation if this has already happened. This command effectively guarantees that all data is flushed
+        to <filename>/var/log/journal</filename> at the time it returns.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--relinquish-var</option></term>
+
+        <listitem><para>Asks the journal daemon for the reverse operation to <option>--flush</option>: if
+        requested the daemon will write further log data to <filename>/run/log/journal/</filename> and stops
+        writing to <filename>/var/log/journal/</filename>. A subsequent call to <option>--flush</option>
+        causes the log output to switch back to <filename>/var/log/journal/</filename>, see
+        above.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--smart-relinquish-var</option></term>
+
+        <listitem><para>Similar to <option>--relinquish-var</option> but executes no operation if the root file
+        system and <filename>/var/lib/journal/</filename> reside on the same mount point. This operation is
+        used during system shutdown in order to make the journal daemon stop writing data to
+        <filename>/var/log/journal/</filename> in case that directory is located on a mount point that needs
+        to be unmounted.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index 689415846642c523fbbec7e014d13bde84095da4..bc71e0645ba762c4495404ffc71fb94cc214008a 100644 (file)
@@ -445,7 +445,10 @@ manpages = [
  ['sd_event_source_set_userdata', '3', ['sd_event_source_get_userdata'], ''],
  ['sd_event_source_unref',
   '3',
-  ['sd_event_source_ref', 'sd_event_source_unrefp'],
+  ['sd_event_source_disable_unref',
+   'sd_event_source_disable_unrefp',
+   'sd_event_source_ref',
+   'sd_event_source_unrefp'],
   ''],
  ['sd_event_wait',
   '3',
index 01e3008eed4a94e87bc95216f72d0035a3bcfc53..81131fa737f373b8f85612dc75d0b8212a5bbbad 100644 (file)
@@ -19,6 +19,8 @@
     <refname>sd_event_source_unref</refname>
     <refname>sd_event_source_unrefp</refname>
     <refname>sd_event_source_ref</refname>
+    <refname>sd_event_source_disable_unref</refname>
+    <refname>sd_event_source_disable_unrefp</refname>
 
     <refpurpose>Increase or decrease event source reference counters</refpurpose>
   </refnamediv>
         <paramdef>sd_event_source *<parameter>source</parameter></paramdef>
       </funcprototype>
 
+      <funcprototype>
+        <funcdef>sd_event_source* <function>sd_event_source_disable_unref</function></funcdef>
+        <paramdef>sd_event_source *<parameter>source</parameter></paramdef>
+      </funcprototype>
+
+      <funcprototype>
+        <funcdef>void <function>sd_event_source_disable_unrefp</function></funcdef>
+        <paramdef>sd_event_source **<parameter>source</parameter></paramdef>
+      </funcprototype>
     </funcsynopsis>
   </refsynopsisdiv>
 
     the passed event source object is
     <constant>NULL</constant>.</para>
 
-    <para>Note that event source objects stay alive and may be
-    dispatched as long as they have a reference counter greater than
-    zero. In order to drop a reference of an event source and make
-    sure the associated event source handler function is not called
-    anymore it is recommended to combine a call of
+    <para>Note that event source objects stay alive and may be dispatched as long as they have a reference
+    counter greater than zero. In order to drop a reference of an event source and make sure the associated
+    event source handler function is not called anymore it is recommended to combine a call of
     <function>sd_event_source_unref()</function> with a prior call to
-    <function>sd_event_source_set_enabled()</function> with
-    <constant>SD_EVENT_OFF</constant>.</para>
+    <function>sd_event_source_set_enabled()</function> with <constant>SD_EVENT_OFF</constant> or call
+    <function>sd_event_source_disable_unref()</function>, see below.</para>
+
+    <para><function>sd_event_source_disable_unref()</function> combines a call to
+    <function>sd_event_source_set_enabled()</function> with <constant>SD_EVENT_OFF</constant> with
+    <function>sd_event_source_unref()</function>. This ensures that the source is disabled before the local
+    reference to it is lost. The <parameter>source</parameter> parameter is allowed to be
+    <constant>NULL</constant>.</para>
+
+    <para><function>sd_event_source_disable_unrefp()</function> is similar to
+    <function>sd_event_source_unrefp()</function>, but in addition disables the source first. This call is
+    useful in conjunction with GCC's and LLVM's
+    <ulink url="https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html">Clean-up Variable
+    Attribute</ulink>. Note that this function is defined as inline function.</para>
   </refsect1>
 
   <refsect1>
     <title>Return Value</title>
 
-    <para><function>sd_event_source_unref()</function> always returns
-    <constant>NULL</constant>.
-    <function>sd_event_source_ref()</function> always returns the
-    event source object passed in.</para>
+    <para><function>sd_event_source_unref()</function> and
+    <function>sd_event_source_disable_unref()</function> always return <constant>NULL</constant>.
+    <function>sd_event_source_ref()</function> always returns the event source object passed in.</para>
   </refsect1>
 
   <xi:include href="libsystemd-pkgconfig.xml" />
index f24bf401595b5c5276a287db79679f416a88bfdc..3ff1a036ff5ebbbd7aa5cae6c8a18e1e3700104e 100644 (file)
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>BlackList=</varname></term>
+          <listitem>
+            <para>A whitespace-separated list of IPv4 addresses. DHCP offers from servers in the list are rejected.</para>
+          </listitem>
+        </varlistentry>
+
       </variablelist>
   </refsect1>
 
             When unset, the kernel's default will be used.</para>
           </listitem>
         </varlistentry>
+        <varlistentry>
+          <term><varname>ProxyARP=</varname></term>
+          <listitem>
+            <para>Takes a boolean. Configures whether proxy ARP to be enabled on this port.
+            When unset, the kernel's default will be used.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><varname>ProxyARPWiFi=</varname></term>
+          <listitem>
+            <para>Takes a boolean. Configures whether proxy ARP to be enabled on this port
+            which meets extended requirements by IEEE 802.11 and Hotspot 2.0 specifications.
+            When unset, the kernel's default will be used.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><varname>MulticastRouter=</varname></term>
+          <listitem>
+            <para>Configures this port for having multicast routers attached. A port with a multicast
+            router will receive all multicast traffic. Takes one of <literal>no</literal>
+            to disable multicast routers on this port, <literal>query</literal> to let the system detect
+            the presence of routers, <literal>permanent</literal> to permanently enable multicast traffic
+            forwarding on this port, or <literal>temporary</literal> to enable multicast routers temporarily
+            on this port, not depending on incoming queries. When unset, the kernel's default will be used.</para>
+          </listitem>
+        </varlistentry>
         <varlistentry>
           <term><varname>Cost=</varname></term>
           <listitem>
index c83a748456e11e77709976391de1c4fbe69056a5..cfb0a7bc72953a7cf207c4f5406d65902588bcf6 100644 (file)
@@ -320,10 +320,7 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
                 fs->service->n_fd_store--;
         }
 
-        if (fs->event_source) {
-                sd_event_source_set_enabled(fs->event_source, SD_EVENT_OFF);
-                sd_event_source_unref(fs->event_source);
-        }
+        sd_event_source_disable_unref(fs->event_source);
 
         free(fs->fdname);
         safe_close(fs->fd);
diff --git a/src/fuzz/fuzz-varlink.c b/src/fuzz/fuzz-varlink.c
new file mode 100644 (file)
index 0000000..31c13e2
--- /dev/null
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <unistd.h>
+
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "hexdecoct.h"
+#include "io-util.h"
+#include "varlink.h"
+#include "log.h"
+
+static FILE *null = NULL;
+
+static int method_something(Varlink *v, JsonVariant *p, VarlinkMethodFlags flags, void *userdata) {
+        json_variant_dump(p, JSON_FORMAT_NEWLINE|JSON_FORMAT_PRETTY, null, NULL);
+        return 0;
+}
+
+static int reply_callback(Varlink *v, JsonVariant *p, const char *error_id, VarlinkReplyFlags flags, void *userdata) {
+        json_variant_dump(p, JSON_FORMAT_NEWLINE|JSON_FORMAT_PRETTY, null, NULL);
+        return 0;
+}
+
+static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        struct iovec *iov = userdata;
+        bool write_eof = false, read_eof = false;
+
+        assert(s);
+        assert(fd >= 0);
+        assert(iov);
+
+        if ((revents & (EPOLLOUT|EPOLLHUP|EPOLLERR)) && iov->iov_len > 0) {
+                ssize_t n;
+
+                /* never write more than 143 bytes a time, to make broken up recv()s on the other side more
+                 * likely, and thus test some additional code paths. */
+                n = send(fd, iov->iov_base, MIN(iov->iov_len, 143U), MSG_NOSIGNAL|MSG_DONTWAIT);
+                if (n < 0) {
+                        if (ERRNO_IS_DISCONNECT(errno))
+                                write_eof = true;
+                        else
+                                assert_se(errno == EAGAIN);
+                } else
+                        IOVEC_INCREMENT(iov, 1, n);
+        }
+
+        if (revents & EPOLLIN) {
+                char c[137];
+                ssize_t n;
+
+                n = recv(fd, c, sizeof(c), MSG_DONTWAIT);
+                if (n < 0) {
+                        if (ERRNO_IS_DISCONNECT(errno))
+                                read_eof = true;
+                        else
+                                assert_se(errno == EAGAIN);
+                } else if (n == 0)
+                        read_eof = true;
+                else
+                        hexdump(null, c, (size_t) n);
+        }
+
+        /* After we wrote everything we could turn off EPOLLOUT. And if we reached read EOF too turn off the
+         * whole thing. */
+        if (write_eof || iov->iov_len == 0) {
+
+                if (read_eof)
+                        assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0);
+                else
+                        assert_se(sd_event_source_set_io_events(s, EPOLLIN) >= 0);
+        }
+
+        return 0;
+}
+
+static int idle_callback(sd_event_source *s, void *userdata) {
+        assert(s);
+
+        /* Called as idle callback when there's nothing else to do anymore */
+        sd_event_exit(sd_event_source_get_event(s), 0);
+        return 0;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+        struct iovec server_iov = IOVEC_MAKE((void*) data, size), client_iov = IOVEC_MAKE((void*) data, size);
+        /* Important: the declaration order matters here! we want that the fds are closed on return after the
+         * event sources, hence we declare the fds first, the event sources second */
+        _cleanup_close_pair_ int server_pair[2] = { -1, -1 }, client_pair[2] = { -1, -1 };
+        _cleanup_(sd_event_source_unrefp) sd_event_source *idle_event_source = NULL,
+                *server_event_source = NULL, *client_event_source = NULL;
+        _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
+        _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL;
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+        log_set_max_level(LOG_CRIT);
+        log_parse_environment();
+
+        assert_se(null = fopen("/dev/null", "we"));
+
+        assert_se(sd_event_default(&e) >= 0);
+
+        /* Test one: write the data as method call to a server */
+        assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, server_pair) >= 0);
+        assert_se(varlink_server_new(&s, 0) >= 0);
+        assert_se(varlink_server_set_description(s, "myserver") >= 0);
+        assert_se(varlink_server_attach_event(s, e, 0) >= 0);
+        assert_se(varlink_server_add_connection(s, server_pair[0], NULL) >= 0);
+        TAKE_FD(server_pair[0]);
+        assert_se(varlink_server_bind_method(s, "io.test.DoSomething", method_something) >= 0);
+        assert_se(sd_event_add_io(e, &server_event_source, server_pair[1], EPOLLIN|EPOLLOUT, io_callback, &server_iov) >= 0);
+
+        /* Test two: write the data as method response to a client */
+        assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, client_pair) >= 0);
+        assert_se(varlink_connect_fd(&c, client_pair[0]) >= 0);
+        TAKE_FD(client_pair[0]);
+        assert_se(varlink_set_description(c, "myclient") >= 0);
+        assert_se(varlink_attach_event(c, e, 0) >= 0);
+        assert_se(varlink_bind_reply(c, reply_callback) >= 0);
+        assert_se(varlink_invoke(c, "io.test.DoSomething", NULL) >= 0);
+        assert_se(sd_event_add_io(e, &client_event_source, client_pair[1], EPOLLIN|EPOLLOUT, io_callback, &client_iov) >= 0);
+
+        assert_se(sd_event_add_defer(e, &idle_event_source, idle_callback, NULL) >= 0);
+        assert_se(sd_event_source_set_priority(idle_event_source, SD_EVENT_PRIORITY_IDLE) >= 0);
+
+        assert_se(sd_event_loop(e) >= 0);
+
+        null = safe_fclose(null);
+
+        return 0;
+}
index a6c6db372b170d75fd9d5e864d23f0dadb11b94b..c88812d1de9f7d83a26f7e9c52d19ac875179007 100644 (file)
@@ -51,6 +51,10 @@ fuzzers += [
          [libshared],
          []],
 
+        [['src/fuzz/fuzz-varlink.c'],
+         [libshared],
+         []],
+
         [['src/fuzz/fuzz-unit-file.c'],
          [libcore,
           libshared],
index 83671cf99b04922fa03d2d611334d4214c41319c..febcc43ce87f224797f6a5fcac2f3a6209a90d5c 100644 (file)
@@ -70,8 +70,7 @@ static int curl_glue_socket_callback(CURLM *curl, curl_socket_t s, int action, v
                         fd = sd_event_source_get_io_fd(io);
                         assert(fd >= 0);
 
-                        sd_event_source_set_enabled(io, SD_EVENT_OFF);
-                        sd_event_source_unref(io);
+                        sd_event_source_disable_unref(io);
 
                         hashmap_remove(g->ios, FD_TO_PTR(s));
                         hashmap_remove(g->translate_fds, FD_TO_PTR(fd));
index 91d1c2921a3f1447ffbe1365d41be4e0dcbeaa38..3e285021bdc90d47276b6d11e972a3971b07e681 100644 (file)
@@ -357,8 +357,7 @@ JournalFile* journal_file_close(JournalFile *f) {
                 if (sd_event_source_get_enabled(f->post_change_timer, NULL) > 0)
                         journal_file_post_change(f);
 
-                (void) sd_event_source_set_enabled(f->post_change_timer, SD_EVENT_OFF);
-                sd_event_source_unref(f->post_change_timer);
+                sd_event_source_disable_unref(f->post_change_timer);
         }
 
         journal_file_set_offline(f, true);
index 72c162259618b7af778923e37cba1f5611c1ac1e..88ee4ee35f69c99edee3e53b08b021401415708f 100644 (file)
@@ -52,6 +52,7 @@
 #include "logs-show.h"
 #include "memory-util.h"
 #include "mkdir.h"
+#include "mountpoint-util.h"
 #include "nulstr-util.h"
 #include "pager.h"
 #include "parse-util.h"
@@ -67,6 +68,7 @@
 #include "tmpfile-util.h"
 #include "unit-name.h"
 #include "user-util.h"
+#include "varlink.h"
 
 #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
 
@@ -167,6 +169,7 @@ static enum {
         ACTION_UPDATE_CATALOG,
         ACTION_LIST_BOOTS,
         ACTION_FLUSH,
+        ACTION_RELINQUISH_VAR,
         ACTION_SYNC,
         ACTION_ROTATE,
         ACTION_VACUUM,
@@ -368,6 +371,8 @@ static int help(void) {
                "     --vacuum-time=TIME      Remove journal files older than specified time\n"
                "     --verify                Verify journal file consistency\n"
                "     --sync                  Synchronize unwritten journal messages to disk\n"
+               "     --relinquish-var        Stop logging to disk, log to temporary file system\n"
+               "     --smart-relinquish-var  Similar, but NOP if log directory is on root mount\n"
                "     --flush                 Flush all journal data from /run into /var\n"
                "     --rotate                Request immediate rotation of the journal files\n"
                "     --header                Show journal header information\n"
@@ -415,6 +420,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UTC,
                 ARG_SYNC,
                 ARG_FLUSH,
+                ARG_RELINQUISH_VAR,
+                ARG_SMART_RELINQUISH_VAR,
                 ARG_ROTATE,
                 ARG_VACUUM_SIZE,
                 ARG_VACUUM_FILES,
@@ -424,65 +431,67 @@ static int parse_argv(int argc, char *argv[]) {
         };
 
         static const struct option options[] = {
-                { "help",           no_argument,       NULL, 'h'                },
-                { "version" ,       no_argument,       NULL, ARG_VERSION        },
-                { "no-pager",       no_argument,       NULL, ARG_NO_PAGER       },
-                { "pager-end",      no_argument,       NULL, 'e'                },
-                { "follow",         no_argument,       NULL, 'f'                },
-                { "force",          no_argument,       NULL, ARG_FORCE          },
-                { "output",         required_argument, NULL, 'o'                },
-                { "all",            no_argument,       NULL, 'a'                },
-                { "full",           no_argument,       NULL, 'l'                },
-                { "no-full",        no_argument,       NULL, ARG_NO_FULL        },
-                { "lines",          optional_argument, NULL, 'n'                },
-                { "no-tail",        no_argument,       NULL, ARG_NO_TAIL        },
-                { "new-id128",      no_argument,       NULL, ARG_NEW_ID128      }, /* deprecated */
-                { "quiet",          no_argument,       NULL, 'q'                },
-                { "merge",          no_argument,       NULL, 'm'                },
-                { "this-boot",      no_argument,       NULL, ARG_THIS_BOOT      }, /* deprecated */
-                { "boot",           optional_argument, NULL, 'b'                },
-                { "list-boots",     no_argument,       NULL, ARG_LIST_BOOTS     },
-                { "dmesg",          no_argument,       NULL, 'k'                },
-                { "system",         no_argument,       NULL, ARG_SYSTEM         },
-                { "user",           no_argument,       NULL, ARG_USER           },
-                { "directory",      required_argument, NULL, 'D'                },
-                { "file",           required_argument, NULL, ARG_FILE           },
-                { "root",           required_argument, NULL, ARG_ROOT           },
-                { "header",         no_argument,       NULL, ARG_HEADER         },
-                { "identifier",     required_argument, NULL, 't'                },
-                { "priority",       required_argument, NULL, 'p'                },
-                { "grep",           required_argument, NULL, 'g'                },
-                { "case-sensitive", optional_argument, NULL, ARG_CASE_SENSITIVE },
-                { "setup-keys",     no_argument,       NULL, ARG_SETUP_KEYS     },
-                { "interval",       required_argument, NULL, ARG_INTERVAL       },
-                { "verify",         no_argument,       NULL, ARG_VERIFY         },
-                { "verify-key",     required_argument, NULL, ARG_VERIFY_KEY     },
-                { "disk-usage",     no_argument,       NULL, ARG_DISK_USAGE     },
-                { "cursor",         required_argument, NULL, 'c'                },
-                { "cursor-file",    required_argument, NULL, ARG_CURSOR_FILE    },
-                { "after-cursor",   required_argument, NULL, ARG_AFTER_CURSOR   },
-                { "show-cursor",    no_argument,       NULL, ARG_SHOW_CURSOR    },
-                { "since",          required_argument, NULL, 'S'                },
-                { "until",          required_argument, NULL, 'U'                },
-                { "unit",           required_argument, NULL, 'u'                },
-                { "user-unit",      required_argument, NULL, ARG_USER_UNIT      },
-                { "field",          required_argument, NULL, 'F'                },
-                { "fields",         no_argument,       NULL, 'N'                },
-                { "catalog",        no_argument,       NULL, 'x'                },
-                { "list-catalog",   no_argument,       NULL, ARG_LIST_CATALOG   },
-                { "dump-catalog",   no_argument,       NULL, ARG_DUMP_CATALOG   },
-                { "update-catalog", no_argument,       NULL, ARG_UPDATE_CATALOG },
-                { "reverse",        no_argument,       NULL, 'r'                },
-                { "machine",        required_argument, NULL, 'M'                },
-                { "utc",            no_argument,       NULL, ARG_UTC            },
-                { "flush",          no_argument,       NULL, ARG_FLUSH          },
-                { "sync",           no_argument,       NULL, ARG_SYNC           },
-                { "rotate",         no_argument,       NULL, ARG_ROTATE         },
-                { "vacuum-size",    required_argument, NULL, ARG_VACUUM_SIZE    },
-                { "vacuum-files",   required_argument, NULL, ARG_VACUUM_FILES   },
-                { "vacuum-time",    required_argument, NULL, ARG_VACUUM_TIME    },
-                { "no-hostname",    no_argument,       NULL, ARG_NO_HOSTNAME    },
-                { "output-fields",  required_argument, NULL, ARG_OUTPUT_FIELDS  },
+                { "help",                 no_argument,       NULL, 'h'                      },
+                { "version" ,             no_argument,       NULL, ARG_VERSION              },
+                { "no-pager",             no_argument,       NULL, ARG_NO_PAGER             },
+                { "pager-end",            no_argument,       NULL, 'e'                      },
+                { "follow",               no_argument,       NULL, 'f'                      },
+                { "force",                no_argument,       NULL, ARG_FORCE                },
+                { "output",               required_argument, NULL, 'o'                      },
+                { "all",                  no_argument,       NULL, 'a'                      },
+                { "full",                 no_argument,       NULL, 'l'                      },
+                { "no-full",              no_argument,       NULL, ARG_NO_FULL              },
+                { "lines",                optional_argument, NULL, 'n'                      },
+                { "no-tail",              no_argument,       NULL, ARG_NO_TAIL              },
+                { "new-id128",            no_argument,       NULL, ARG_NEW_ID128            }, /* deprecated */
+                { "quiet",                no_argument,       NULL, 'q'                      },
+                { "merge",                no_argument,       NULL, 'm'                      },
+                { "this-boot",            no_argument,       NULL, ARG_THIS_BOOT            }, /* deprecated */
+                { "boot",                 optional_argument, NULL, 'b'                      },
+                { "list-boots",           no_argument,       NULL, ARG_LIST_BOOTS           },
+                { "dmesg",                no_argument,       NULL, 'k'                      },
+                { "system",               no_argument,       NULL, ARG_SYSTEM               },
+                { "user",                 no_argument,       NULL, ARG_USER                 },
+                { "directory",            required_argument, NULL, 'D'                      },
+                { "file",                 required_argument, NULL, ARG_FILE                 },
+                { "root",                 required_argument, NULL, ARG_ROOT                 },
+                { "header",               no_argument,       NULL, ARG_HEADER               },
+                { "identifier",           required_argument, NULL, 't'                      },
+                { "priority",             required_argument, NULL, 'p'                      },
+                { "grep",                 required_argument, NULL, 'g'                      },
+                { "case-sensitive",       optional_argument, NULL, ARG_CASE_SENSITIVE       },
+                { "setup-keys",           no_argument,       NULL, ARG_SETUP_KEYS           },
+                { "interval",             required_argument, NULL, ARG_INTERVAL             },
+                { "verify",               no_argument,       NULL, ARG_VERIFY               },
+                { "verify-key",           required_argument, NULL, ARG_VERIFY_KEY           },
+                { "disk-usage",           no_argument,       NULL, ARG_DISK_USAGE           },
+                { "cursor",               required_argument, NULL, 'c'                      },
+                { "cursor-file",          required_argument, NULL, ARG_CURSOR_FILE          },
+                { "after-cursor",         required_argument, NULL, ARG_AFTER_CURSOR         },
+                { "show-cursor",          no_argument,       NULL, ARG_SHOW_CURSOR          },
+                { "since",                required_argument, NULL, 'S'                      },
+                { "until",                required_argument, NULL, 'U'                      },
+                { "unit",                 required_argument, NULL, 'u'                      },
+                { "user-unit",            required_argument, NULL, ARG_USER_UNIT            },
+                { "field",                required_argument, NULL, 'F'                      },
+                { "fields",               no_argument,       NULL, 'N'                      },
+                { "catalog",              no_argument,       NULL, 'x'                      },
+                { "list-catalog",         no_argument,       NULL, ARG_LIST_CATALOG         },
+                { "dump-catalog",         no_argument,       NULL, ARG_DUMP_CATALOG         },
+                { "update-catalog",       no_argument,       NULL, ARG_UPDATE_CATALOG       },
+                { "reverse",              no_argument,       NULL, 'r'                      },
+                { "machine",              required_argument, NULL, 'M'                      },
+                { "utc",                  no_argument,       NULL, ARG_UTC                  },
+                { "flush",                no_argument,       NULL, ARG_FLUSH                },
+                { "relinquish-var",       no_argument,       NULL, ARG_RELINQUISH_VAR       },
+                { "smart-relinquish-var", no_argument,       NULL, ARG_SMART_RELINQUISH_VAR },
+                { "sync",                 no_argument,       NULL, ARG_SYNC                 },
+                { "rotate",               no_argument,       NULL, ARG_ROTATE               },
+                { "vacuum-size",          required_argument, NULL, ARG_VACUUM_SIZE          },
+                { "vacuum-files",         required_argument, NULL, ARG_VACUUM_FILES         },
+                { "vacuum-time",          required_argument, NULL, ARG_VACUUM_TIME          },
+                { "no-hostname",          no_argument,       NULL, ARG_NO_HOSTNAME          },
+                { "output-fields",        required_argument, NULL, ARG_OUTPUT_FIELDS        },
                 {}
         };
 
@@ -914,6 +923,35 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_action = ACTION_FLUSH;
                         break;
 
+                case ARG_SMART_RELINQUISH_VAR: {
+                        int root_mnt_id, log_mnt_id;
+
+                        /* Try to be smart about relinquishing access to /var/log/journal/ during shutdown:
+                         * if it's on the same mount as the root file system there's no point in
+                         * relinquishing access and we can leave journald write to it until the very last
+                         * moment. */
+
+                        r = path_get_mnt_id("/", &root_mnt_id);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to get root mount ID, ignoring: %m");
+                        else {
+                                r = path_get_mnt_id("/var/log/journal/", &log_mnt_id);
+                                if (r < 0)
+                                        log_debug_errno(r, "Failed to get journal directory mount ID, ignoring: %m");
+                                else if (root_mnt_id == log_mnt_id) {
+                                        log_debug("/var/log/journal/ is on root file system, not relinquishing access to /var.");
+                                        return 0;
+                                } else
+                                        log_debug("/var/log/journal/ is not on the root file system, relinquishing access to it.");
+                        }
+
+                        _fallthrough_;
+                }
+
+                case ARG_RELINQUISH_VAR:
+                        arg_action = ACTION_RELINQUISH_VAR;
+                        break;
+
                 case ARG_ROTATE:
                         arg_action = arg_action == ACTION_VACUUM ? ACTION_ROTATE_AND_VACUUM : ACTION_ROTATE;
                         break;
@@ -1901,156 +1939,41 @@ static int verify(sd_journal *j) {
         return r;
 }
 
-static int watch_run_systemd_journal(uint32_t mask) {
-        _cleanup_close_ int watch_fd = -1;
-
-        (void) mkdir_p("/run/systemd/journal", 0755);
-
-        watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
-        if (watch_fd < 0)
-                return log_error_errno(errno, "Failed to create inotify object: %m");
-
-        if (inotify_add_watch(watch_fd, "/run/systemd/journal", mask) < 0)
-                return log_error_errno(errno, "Failed to watch \"/run/systemd/journal\": %m");
-
-        return TAKE_FD(watch_fd);
-}
-
-static int flush_to_var(void) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_close_ int watch_fd = -1;
+static int simple_varlink_call(const char *option, const char *method) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        const char *error;
         int r;
 
-        if (arg_machine) {
-                log_error("--flush is not supported in conjunction with --machine=.");
-                return -EOPNOTSUPP;
-        }
-
-        /* Quick exit */
-        if (access("/run/systemd/journal/flushed", F_OK) >= 0)
-                return 0;
+        if (arg_machine)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "%s is not supported in conjunction with --machine=.", option);
 
-        /* OK, let's actually do the full logic, send SIGUSR1 to the
-         * daemon and set up inotify to wait for the flushed file to appear */
-        r = bus_connect_system_systemd(&bus);
+        r = varlink_connect_address(&link, "/run/systemd/journal/io.systemd.journal");
         if (r < 0)
-                return log_error_errno(r, "Failed to get D-Bus connection: %m");
-
-        r = sd_bus_call_method(
-                        bus,
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        "KillUnit",
-                        &error,
-                        NULL,
-                        "ssi", "systemd-journald.service", "main", SIGUSR1);
-        if (r < 0)
-                return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r));
+                return log_error_errno(r, "Failed to connect to /run/systemd/journal/io.systemd.journal: %m");
 
-        watch_fd = watch_run_systemd_journal(IN_CREATE|IN_DONT_FOLLOW|IN_ONLYDIR);
-        if (watch_fd < 0)
-                return watch_fd;
+        (void) varlink_set_description(link, "journal");
 
-        for (;;) {
-                if (access("/run/systemd/journal/flushed", F_OK) >= 0)
-                        return 0;
-
-                if (errno != ENOENT)
-                        return log_error_errno(errno, "Failed to check for existence of /run/systemd/journal/flushed: %m");
-
-                r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to wait for event: %m");
+        r = varlink_call(link, method, NULL, NULL, &error, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to execute varlink call: %s", error);
 
-                r = flush_fd(watch_fd);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to flush inotify events: %m");
-        }
+        return 0;
 }
 
-static int send_signal_and_wait(int sig, const char *watch_path) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_close_ int watch_fd = -1;
-        usec_t start;
-        int r;
-
-        if (arg_machine) {
-                log_error("--sync and --rotate are not supported in conjunction with --machine=.");
-                return -EOPNOTSUPP;
-        }
-
-        start = now(CLOCK_MONOTONIC);
-
-        /* This call sends the specified signal to journald, and waits
-         * for acknowledgment by watching the mtime of the specified
-         * flag file. This is used to trigger syncing or rotation and
-         * then wait for the operation to complete. */
-
-        for (;;) {
-                usec_t tstamp;
-
-                /* See if a sync happened by now. */
-                r = read_timestamp_file(watch_path, &tstamp);
-                if (r < 0 && r != -ENOENT)
-                        return log_error_errno(r, "Failed to read %s: %m", watch_path);
-                if (r >= 0 && tstamp >= start)
-                        return 0;
-
-                /* Let's ask for a sync, but only once. */
-                if (!bus) {
-                        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-
-                        r = bus_connect_system_systemd(&bus);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to get D-Bus connection: %m");
-
-                        r = sd_bus_call_method(
-                                        bus,
-                                        "org.freedesktop.systemd1",
-                                        "/org/freedesktop/systemd1",
-                                        "org.freedesktop.systemd1.Manager",
-                                        "KillUnit",
-                                        &error,
-                                        NULL,
-                                        "ssi", "systemd-journald.service", "main", sig);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r));
-
-                        continue;
-                }
-
-                /* Let's install the inotify watch, if we didn't do that yet. */
-                if (watch_fd < 0) {
-                        watch_fd = watch_run_systemd_journal(IN_MOVED_TO|IN_DONT_FOLLOW|IN_ONLYDIR);
-                        if (watch_fd < 0)
-                                return watch_fd;
-
-                        /* Recheck the flag file immediately, so that we don't miss any event since the last check. */
-                        continue;
-                }
-
-                /* OK, all preparatory steps done, let's wait until inotify reports an event. */
-
-                r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to wait for event: %m");
-
-                r = flush_fd(watch_fd);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to flush inotify events: %m");
-        }
+static int flush_to_var(void) {
+        return simple_varlink_call("--flush", "io.systemd.Journal.FlushToVar");
+}
 
-        return 0;
+static int relinquish_var(void) {
+        return simple_varlink_call("--relinquish-var", "io.systemd.Journal.RelinquishVar");
 }
 
 static int rotate(void) {
-        return send_signal_and_wait(SIGUSR2, "/run/systemd/journal/rotated");
+        return simple_varlink_call("--rotate", "io.systemd.Journal.Rotate");
 }
 
 static int sync_journal(void) {
-        return send_signal_and_wait(SIGRTMIN+1, "/run/systemd/journal/synced");
+        return simple_varlink_call("--sync", "io.systemd.Journal.Synchronize");
 }
 
 static int wait_for_change(sd_journal *j, int poll_fd) {
@@ -2160,6 +2083,10 @@ int main(int argc, char *argv[]) {
                 r = flush_to_var();
                 goto finish;
 
+        case ACTION_RELINQUISH_VAR:
+                r = relinquish_var();
+                goto finish;
+
         case ACTION_SYNC:
                 r = sync_journal();
                 goto finish;
index ee6c25541cf6445c63a8a0026fa9f32a25564b44..51e7dedad59fce3ff992c6cf7d9a0beb3f021b0e 100644 (file)
@@ -286,13 +286,14 @@ static bool flushed_flag_is_set(void) {
         return access("/run/systemd/journal/flushed", F_OK) >= 0;
 }
 
-static int system_journal_open(Server *s, bool flush_requested) {
+static int system_journal_open(Server *s, bool flush_requested, bool relinquish_requested) {
         const char *fn;
         int r = 0;
 
         if (!s->system_journal &&
             IN_SET(s->storage, STORAGE_PERSISTENT, STORAGE_AUTO) &&
-            (flush_requested || flushed_flag_is_set())) {
+            (flush_requested || flushed_flag_is_set()) &&
+            !relinquish_requested) {
 
                 /* If in auto mode: first try to create the machine
                  * path, but not the prefix.
@@ -334,7 +335,7 @@ static int system_journal_open(Server *s, bool flush_requested) {
 
                 fn = strjoina(s->runtime_storage.path, "/system.journal");
 
-                if (s->system_journal) {
+                if (s->system_journal && !relinquish_requested) {
 
                         /* Try to open the runtime journal, but only
                          * if it already exists, so that we can flush
@@ -389,7 +390,7 @@ static JournalFile* find_journal(Server *s, uid_t uid) {
          * else that's left the journals as NULL).
          *
          * Fixes https://github.com/systemd/systemd/issues/3968 */
-        (void) system_journal_open(s, false);
+        (void) system_journal_open(s, false, false);
 
         /* We split up user logs only on /var, not on /run. If the
          * runtime file is open, we write to it exclusively, in order
@@ -1117,7 +1118,7 @@ int server_flush_to_var(Server *s, bool require_flag_file) {
         char ts[FORMAT_TIMESPAN_MAX];
         usec_t start;
         unsigned n = 0;
-        int r;
+        int r, k;
 
         assert(s);
 
@@ -1130,7 +1131,7 @@ int server_flush_to_var(Server *s, bool require_flag_file) {
         if (require_flag_file && !flushed_flag_is_set())
                 return 0;
 
-        (void) system_journal_open(s, true);
+        (void) system_journal_open(s, true, false);
 
         if (!s->system_journal)
                 return 0;
@@ -1209,9 +1210,36 @@ finish:
                                           n),
                               NULL);
 
+        k = touch("/run/systemd/journal/flushed");
+        if (k < 0)
+                log_warning_errno(k, "Failed to touch /run/systemd/journal/flushed, ignoring: %m");
+
         return r;
 }
 
+static int server_relinquish_var(Server *s) {
+        assert(s);
+
+        if (s->storage == STORAGE_NONE)
+                return 0;
+
+        if (s->runtime_journal && !s->system_journal)
+                return 0;
+
+        log_debug("Relinquishing /var...");
+
+        (void) system_journal_open(s, false, true);
+
+        s->system_journal = journal_file_close(s->system_journal);
+        ordered_hashmap_clear_with_destructor(s->user_journals, journal_file_close);
+        set_clear_with_destructor(s->deferred_closes, journal_file_close);
+
+        if (unlink("/run/systemd/journal/flushed") < 0 && errno != ENOENT)
+                log_warning_errno(errno, "Failed to unlink /run/systemd/journal/flushed, ignoring: %m");
+
+        return 0;
+}
+
 int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
         Server *s = userdata;
         struct ucred *ucred = NULL;
@@ -1330,33 +1358,32 @@ int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void
         return 0;
 }
 
-static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
-        Server *s = userdata;
-        int r;
-
+static void server_full_flush(Server *s) {
         assert(s);
 
-        log_info("Received request to flush runtime journal from PID " PID_FMT, si->ssi_pid);
-
         (void) server_flush_to_var(s, false);
         server_sync(s);
         server_vacuum(s, false);
 
-        r = touch("/run/systemd/journal/flushed");
-        if (r < 0)
-                log_warning_errno(r, "Failed to touch /run/systemd/journal/flushed, ignoring: %m");
-
         server_space_usage_message(s, NULL);
-        return 0;
 }
 
-static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
+static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
         Server *s = userdata;
+
+        assert(s);
+
+        log_info("Received SIGUSR1 signal from PID " PID_FMT ", as request to flush runtime journal.", si->ssi_pid);
+        server_full_flush(s);
+
+        return 0;
+}
+
+static void server_full_rotate(Server *s) {
         int r;
 
         assert(s);
 
-        log_info("Received request to rotate journal from PID " PID_FMT, si->ssi_pid);
         server_rotate(s);
         server_vacuum(s, true);
 
@@ -1369,6 +1396,15 @@ static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *
         r = write_timestamp_file_atomic("/run/systemd/journal/rotated", now(CLOCK_MONOTONIC));
         if (r < 0)
                 log_warning_errno(r, "Failed to write /run/systemd/journal/rotated, ignoring: %m");
+}
+
+static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
+        Server *s = userdata;
+
+        assert(s);
+
+        log_info("Received SIGUSR2 signal from PID " PID_FMT ", as request to rotate journal.", si->ssi_pid);
+        server_full_rotate(s);
 
         return 0;
 }
@@ -1384,14 +1420,11 @@ static int dispatch_sigterm(sd_event_source *es, const struct signalfd_siginfo *
         return 0;
 }
 
-static int dispatch_sigrtmin1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
-        Server *s = userdata;
+static void server_full_sync(Server *s) {
         int r;
 
         assert(s);
 
-        log_debug("Received request to sync from PID " PID_FMT, si->ssi_pid);
-
         server_sync(s);
 
         /* Let clients know when the most recent sync happened. */
@@ -1399,6 +1432,17 @@ static int dispatch_sigrtmin1(sd_event_source *es, const struct signalfd_siginfo
         if (r < 0)
                 log_warning_errno(r, "Failed to write /run/systemd/journal/synced, ignoring: %m");
 
+        return;
+}
+
+static int dispatch_sigrtmin1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
+        Server *s = userdata;
+
+        assert(s);
+
+        log_debug("Received SIGRTMIN1 signal from PID " PID_FMT ", as request to sync.", si->ssi_pid );
+        server_full_sync(s);
+
         return 0;
 }
 
@@ -1803,6 +1847,151 @@ static int server_connect_notify(Server *s) {
         return 0;
 }
 
+static int synchronize_second_half(sd_event_source *event_source, void *userdata) {
+        Varlink *link = userdata;
+        Server *s;
+        int r;
+
+        assert(link);
+        assert_se(s = varlink_get_userdata(link));
+
+        /* This is the "second half" of the Synchronize() varlink method. This function is called as deferred
+         * event source at a low priority to ensure the synchronization completes after all queued log
+         * messages are processed. */
+        server_full_sync(s);
+
+        /* Let's get rid of the event source now, by marking it as non-floating again. It then has no ref
+         * anymore and is immediately destroyed after we return from this function, i.e. from this event
+         * source handler at the end. */
+        r = sd_event_source_set_floating(event_source, false);
+        if (r < 0)
+                return log_error_errno(r, "Failed to mark event source as non-floating: %m");
+
+        return varlink_reply(link, NULL);
+}
+
+static void synchronize_destroy(void *userdata) {
+        varlink_unref(userdata);
+}
+
+static int vl_method_synchronize(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+        _cleanup_(sd_event_source_unrefp) sd_event_source *event_source = NULL;
+        Server *s = userdata;
+        int r;
+
+        assert(link);
+        assert(s);
+
+        if (json_variant_elements(parameters) > 0)
+                return varlink_error_invalid_parameter(link, parameters);
+
+        log_info("Received client request to rotate journal.");
+
+        /* We don't do the main work now, but instead enqueue a deferred event loop job which will do
+         * it. That job is scheduled at low priority, so that we return from this method call only after all
+         * queued but not processed log messages are written to disk, so that this method call returning can
+         * be used as nice synchronization point. */
+        r = sd_event_add_defer(s->event, &event_source, synchronize_second_half, link);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate defer event source: %m");
+
+        r = sd_event_source_set_destroy_callback(event_source, synchronize_destroy);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set event source destroy callback: %m");
+
+        varlink_ref(link); /* The varlink object is now left to the destroy callack to unref */
+
+        r = sd_event_source_set_priority(event_source, SD_EVENT_PRIORITY_NORMAL+15);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set defer event source priority: %m");
+
+        /* Give up ownership of this event source. It will now be destroyed along with event loop itself,
+         * unless it destroys itself earlier. */
+        r = sd_event_source_set_floating(event_source, true);
+        if (r < 0)
+                return log_error_errno(r, "Failed to mark event source as floating: %m");
+
+        (void) sd_event_source_set_description(event_source, "deferred-sync");
+
+        return 0;
+}
+
+static int vl_method_rotate(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+        Server *s = userdata;
+
+        assert(link);
+        assert(s);
+
+        if (json_variant_elements(parameters) > 0)
+                return varlink_error_invalid_parameter(link, parameters);
+
+        log_info("Received client request to rotate journal.");
+        server_full_rotate(s);
+
+        return varlink_reply(link, NULL);
+}
+
+static int vl_method_flush_to_var(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+        Server *s = userdata;
+
+        assert(link);
+        assert(s);
+
+        if (json_variant_elements(parameters) > 0)
+                return varlink_error_invalid_parameter(link, parameters);
+
+        log_info("Received client request to flush runtime journal.");
+        server_full_flush(s);
+
+        return varlink_reply(link, NULL);
+}
+
+static int vl_method_relinquish_var(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+        Server *s = userdata;
+
+        assert(link);
+        assert(s);
+
+        if (json_variant_elements(parameters) > 0)
+                return varlink_error_invalid_parameter(link, parameters);
+
+        log_info("Received client request to relinquish /var access.");
+        server_relinquish_var(s);
+
+        return varlink_reply(link, NULL);
+}
+
+static int server_open_varlink(Server *s) {
+        int r;
+
+        assert(s);
+
+        r = varlink_server_new(&s->varlink_server, VARLINK_SERVER_ROOT_ONLY);
+        if (r < 0)
+                return r;
+
+        varlink_server_set_userdata(s->varlink_server, s);
+
+        r = varlink_server_bind_method_many(
+                        s->varlink_server,
+                        "io.systemd.Journal.Synchronize",   vl_method_synchronize,
+                        "io.systemd.Journal.Rotate",        vl_method_rotate,
+                        "io.systemd.Journal.FlushToVar",    vl_method_flush_to_var,
+                        "io.systemd.Journal.RelinquishVar", vl_method_relinquish_var);
+        if (r < 0)
+                return r;
+
+        r = varlink_server_listen_address(s->varlink_server, "/run/systemd/journal/io.systemd.journal", 0600);
+        if (r < 0)
+                return r;
+
+        r = varlink_server_attach_event(s->varlink_server, s->event, SD_EVENT_PRIORITY_NORMAL);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 int server_init(Server *s) {
         _cleanup_fdset_free_ FDSet *fds = NULL;
         int n, r, fd;
@@ -1973,6 +2162,10 @@ int server_init(Server *s) {
                         return r;
         }
 
+        r = server_open_varlink(s);
+        if (r < 0)
+                return r;
+
         r = server_open_kernel_seqnum(s);
         if (r < 0)
                 return r;
@@ -2006,7 +2199,7 @@ int server_init(Server *s) {
 
         (void) client_context_acquire_default(s);
 
-        return system_journal_open(s, false);
+        return system_journal_open(s, false, false);
 }
 
 void server_maybe_append_tags(Server *s) {
@@ -2043,6 +2236,8 @@ void server_done(Server *s) {
 
         ordered_hashmap_free_with_destructor(s->user_journals, journal_file_close);
 
+        varlink_server_unref(s->varlink_server);
+
         sd_event_source_unref(s->syslog_event_source);
         sd_event_source_unref(s->native_event_source);
         sd_event_source_unref(s->stdout_event_source);
index 5f0b3dd4ac2a8a98c745dab65ea3fc374841a441..6ce9f269ffc5b294567823337222faaf5bb391d1 100644 (file)
@@ -17,6 +17,7 @@ typedef struct Server Server;
 #include "list.h"
 #include "prioq.h"
 #include "time-util.h"
+#include "varlink.h"
 
 typedef enum Storage {
         STORAGE_AUTO,
@@ -166,6 +167,8 @@ struct Server {
 
         ClientContext *my_context; /* the context of journald itself */
         ClientContext *pid1_context; /* the context of PID 1 */
+
+        VarlinkServer *varlink_server;
 };
 
 #define SERVER_MACHINE_ID(s) ((s)->machine_id_field + STRLEN("_MACHINE_ID="))
index 33a8ef799975bef06b05b11b1b87d41427efb3f1..84ce8e0da8196be4e5ff0ed51c4214a82d7285fa 100644 (file)
@@ -532,7 +532,7 @@ int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempt
 int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         assert_return(client, -EINVAL);
 
-        if (!IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
+        if (!IN_SET(client->state, DHCP_STATE_SELECTING, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
                 return -EADDRNOTAVAIL;
 
         if (ret)
@@ -541,11 +541,13 @@ int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         return 0;
 }
 
-static void client_notify(sd_dhcp_client *client, int event) {
+static int client_notify(sd_dhcp_client *client, int event) {
         assert(client);
 
         if (client->callback)
-                client->callback(client, event, client->userdata);
+                return client->callback(client, event, client->userdata);
+
+        return 0;
 }
 
 static int client_initialize(sd_dhcp_client *client) {
@@ -1328,6 +1330,9 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_
         sd_dhcp_lease_unref(client->lease);
         client->lease = TAKE_PTR(lease);
 
+        if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
+                return -ENOMSG;
+
         log_dhcp_client(client, "OFFER");
 
         return 0;
index 0431e2c3f568605ed2d3e12e8cdd2d6d154b74a1..5f31d24d20bd4d3e750d5bd88d02a9c835909746 100644 (file)
@@ -418,7 +418,7 @@ static uint8_t test_addr_acq_ack[] = {
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 };
 
-static void test_addr_acq_acquired(sd_dhcp_client *client, int event,
+static int test_addr_acq_acquired(sd_dhcp_client *client, int event,
                                    void *userdata) {
         sd_event *e = userdata;
         sd_dhcp_lease *lease;
@@ -426,7 +426,7 @@ static void test_addr_acq_acquired(sd_dhcp_client *client, int event,
         const struct in_addr *addrs;
 
         assert_se(client);
-        assert_se(event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE);
+        assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING));
 
         assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0);
         assert_se(lease);
@@ -447,6 +447,8 @@ static void test_addr_acq_acquired(sd_dhcp_client *client, int event,
                 printf("  DHCP address acquired\n");
 
         sd_event_exit(e, 0);
+
+        return 0;
 }
 
 static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) {
index a9ab0605ce14427d8b0219e91d7b0014fd1b14a9..5ec42e0f1f826eb602ce121e2650012b3250f930 100644 (file)
@@ -680,4 +680,5 @@ global:
 LIBSYSTEMD_243 {
 global:
         sd_bus_object_vtable_format;
+        sd_event_source_disable_unref;
 } LIBSYSTEMD_241;
index 50017a9517baac9ef06934413ac0c443e30ef521..09285c19d86b6d29047128a2be01e2ee3c0bc13b 100644 (file)
@@ -339,6 +339,12 @@ fail:
 
 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free);
 
+_public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) {
+        if (s)
+                (void) sd_event_source_set_enabled(s, SD_EVENT_OFF);
+        return sd_event_source_unref(s);
+}
+
 static bool event_pid_changed(sd_event *e) {
         assert(e);
 
index 12f59cd94695c57c883dd1e2ebf53e716af63eea..038272f107efd4d2719633f15c17451b1c26b0cd 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
+#include <netinet/in.h>
 #include <linux/if_bonding.h>
 
 #include "in-addr-util.h"
index 2d4481e0602d7add39183caa6952205f3ad11e58..b5d4690f2149db1b326c22d716458fc2b537538f 100644 (file)
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <arpa/inet.h>
 #include <net/if.h>
+#include <netinet/in.h>
 #include <linux/ip.h>
 
 #include "conf-parser.h"
index 51eeac41bd237e46e50bef874ae0770793757e98..a93d2dc02f711e05188ed06cc909ea6a1b736e89 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
+#include <netinet/in.h>
 #include <linux/fou.h>
 
 #include "in-addr-util.h"
index 78f09dbb2d58a9915424bbc37239aaa579e51c52..eb67b5c1959d425127e1ce629c9911136d0affea 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
+#include <netinet/in.h>
 #include <linux/if_link.h>
 
 #include "netdev/netdev.h"
index 2a74d89e5d229a4159a56bc3b5bed774cc0f3f3f..6ac23052bb3ce94ed37a6ba4852ac62869d63468 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <arpa/inet.h>
+#include <netinet/in.h>
 #include <linux/l2tp.h>
 #include <linux/genetlink.h>
 
index a97c924c5d236e240f6f691585f5d88d9fe9116d..d314b9870344d278e1510841885d7ef5a1b3c919 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
+#include <netinet/in.h>
 #include <linux/l2tp.h>
 
 #include "in-addr-util.h"
index ccc37cded4684494603a2b8587e698558e1ed1fc..c82a6fd0ac16a4580eb517ddf99c5919da0a91c3 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <arpa/inet.h>
+#include <netinet/in.h>
 #include <linux/if_ether.h>
 #include <linux/if_macsec.h>
 #include <linux/genetlink.h>
index 2bd08ac500950c744a8b0619c494317b888119d6..2a3443a6d4e6dcba45ee7926d5546194d3331095 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
+#include <netinet/in.h>
 #include <linux/if_macsec.h>
 
 #include "in-addr-util.h"
index 84f6af8578fc4628d089a73e0c1476bb3990a746..e69a009c315a2deb1d097111885b5d963177ceae 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <arpa/inet.h>
+#include <netinet/in.h>
 #include <linux/fou.h>
 #include <linux/ip.h>
 #include <linux/if_tunnel.h>
index 951138d257a37bdd70c7b47b12b6dd33e13bd7d2..d08acf0c664d740cd006f20cac35f2d57baa5426 100644 (file)
@@ -2,12 +2,12 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <linux/if_tun.h>
 #include <net/if.h>
 #include <netinet/if_ether.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <linux/if_tun.h>
 
 #include "alloc-util.h"
 #include "fd-util.h"
index 6f62686d0801e8491f9d5056eee38a4dfa55b89e..671b5cb6390a2dadfea2ef2a6a637557b6b06b30 100644 (file)
@@ -3,6 +3,7 @@
 
 typedef struct VCan VCan;
 
+#include <netinet/in.h>
 #include <linux/can/netlink.h>
 
 #include "netdev/netdev.h"
index 3ad95ade0f2be6fbfaa48d28ed78ce8deaa134e3..a0a0b8e39ec4599a4f772c6736c874f2f0cb59b0 100644 (file)
@@ -1,8 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include <errno.h>
-#include <linux/veth.h>
 #include <net/if.h>
+#include <linux/veth.h>
 
 #include "sd-netlink.h"
 
index dd548b338aebc5eebeea7fb268867768b1498172..4d939ab0456adeeba34cc723be728b75d7e9c924 100644 (file)
@@ -1,8 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include <errno.h>
-#include <linux/if_vlan.h>
 #include <net/if.h>
+#include <linux/if_vlan.h>
 
 #include "netdev/vlan.h"
 #include "vlan-util.h"
index 862f2a99c4c7b3f4cd74f9a20c9aecc246aab814..ce336c985acdc6b6185fc8f42b96dd5dacc799aa 100644 (file)
@@ -2,6 +2,7 @@
 
 typedef struct Wireguard Wireguard;
 
+#include <netinet/in.h>
 #include <linux/wireguard.h>
 
 #include "in-addr-util.h"
index 01f4e955c85e9b899cf4e4b1c0e45866de9ec3b7..23831d25b6df4f3d22773af04a7ff21099b14ec4 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <netinet/ether.h>
+#include <netinet/in.h>
 #include <linux/if.h>
 
 #include "alloc-util.h"
@@ -512,7 +512,35 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
         return 0;
 }
 
-static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
+static int dhcp_server_is_black_listed(Link *link, sd_dhcp_client *client) {
+        sd_dhcp_lease *lease;
+        struct in_addr addr;
+        int r;
+
+        assert(link);
+        assert(link->network);
+        assert(client);
+
+        r = sd_dhcp_client_get_lease(client, &lease);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to get DHCP lease: %m");
+
+        r = sd_dhcp_lease_get_server_identifier(lease, &addr);
+        if (r < 0)
+                return log_link_debug_errno(link, r, "Failed to get DHCP server ip address: %m");
+
+        if (set_contains(link->network->dhcp_black_listed_ip, UINT32_TO_PTR(addr.s_addr))) {
+                log_struct(LOG_DEBUG,
+                           LOG_LINK_INTERFACE(link),
+                           LOG_LINK_MESSAGE(link, "DHCPv4 ip '%u.%u.%u.%u' found in black listed ip addresses, ignoring offer",
+                                            ADDRESS_FMT_VAL(addr)));
+                return true;
+        }
+
+        return false;
+}
+
+static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
         Link *link = userdata;
         int r = 0;
 
@@ -521,7 +549,7 @@ static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
         assert(link->manager);
 
         if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
-                return;
+                return 0;
 
         switch (event) {
                 case SD_DHCP_CLIENT_EVENT_STOP:
@@ -532,10 +560,8 @@ static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
                                 log_link_debug(link, "DHCP client is stopped. Acquiring IPv4 link-local address");
 
                                 r = sd_ipv4ll_start(link->ipv4ll);
-                                if (r < 0) {
-                                        log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m");
-                                        return;
-                                }
+                                if (r < 0)
+                                        return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m");
                         }
 
                         _fallthrough_;
@@ -544,14 +570,14 @@ static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
 
                         if (link->network->dhcp_critical) {
                                 log_link_error(link, "DHCPv4 connection considered system critical, ignoring request to reconfigure it.");
-                                return;
+                                return 0;
                         }
 
                         if (link->dhcp_lease) {
                                 r = dhcp_lease_lost(link);
                                 if (r < 0) {
                                         link_enter_failed(link);
-                                        return;
+                                        return r;
                                 }
                         }
 
@@ -559,7 +585,7 @@ static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
                                 r = dhcp_lease_acquired(client, link);
                                 if (r < 0) {
                                         link_enter_failed(link);
-                                        return;
+                                        return r;
                                 }
                         }
 
@@ -568,16 +594,23 @@ static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
                         r = dhcp_lease_renew(client, link);
                         if (r < 0) {
                                 link_enter_failed(link);
-                                return;
+                                return r;
                         }
                         break;
                 case SD_DHCP_CLIENT_EVENT_IP_ACQUIRE:
                         r = dhcp_lease_acquired(client, link);
                         if (r < 0) {
                                 link_enter_failed(link);
-                                return;
+                                return r;
                         }
                         break;
+                case SD_DHCP_CLIENT_EVENT_SELECTING:
+                        r = dhcp_server_is_black_listed(link, client);
+                        if (r < 0)
+                                return r;
+                        if (r != 0)
+                                return -ENOMSG;
+                        break;
                 default:
                         if (event < 0)
                                 log_link_warning_errno(link, event, "DHCP error: Client failed: %m");
@@ -586,7 +619,7 @@ static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
                         break;
         }
 
-        return;
+        return 0;
 }
 
 static int dhcp4_set_hostname(Link *link) {
index 7db59d3acad6176deff08ece300aa23357797d8e..afa3e2cfd68a883d06aa0247825585c337ee4f92 100644 (file)
@@ -3,7 +3,7 @@
   Copyright Â© 2014 Intel Corporation. All rights reserved.
 ***/
 
-#include <netinet/ether.h>
+#include <netinet/in.h>
 #include <linux/if.h>
 #include "sd-radv.h"
 
@@ -418,7 +418,7 @@ static int dhcp6_address_change(
                 uint32_t lifetime_valid) {
 
         _cleanup_(address_freep) Address *addr = NULL;
-        char buffer[INET6_ADDRSTRLEN];
+        _cleanup_free_ char *buffer = NULL;
         int r;
 
         r = address_new(&addr);
@@ -426,7 +426,7 @@ static int dhcp6_address_change(
                 return r;
 
         addr->family = AF_INET6;
-        memcpy(&addr->in_addr.in6, ip6_addr, sizeof(*ip6_addr));
+        addr->in_addr.in6 = *ip6_addr;
 
         addr->flags = IFA_F_NOPREFIXROUTE;
         addr->prefixlen = 128;
@@ -434,10 +434,10 @@ static int dhcp6_address_change(
         addr->cinfo.ifa_prefered = lifetime_preferred;
         addr->cinfo.ifa_valid = lifetime_valid;
 
+        (void) in_addr_to_string(addr->family, &addr->in_addr, &buffer);
         log_link_info(link,
                       "DHCPv6 address %s/%d timeout preferred %d valid %d",
-                      inet_ntop(AF_INET6, &addr->in_addr.in6, buffer, sizeof(buffer)),
-                      addr->prefixlen, lifetime_preferred, lifetime_valid);
+                      strnull(buffer), addr->prefixlen, lifetime_preferred, lifetime_valid);
 
         r = address_configure(addr, link, dhcp6_address_handler, true);
         if (r < 0)
index 79e90f2f5af640b254857688144efd2cf92f8888..8fb8a64e6d2ff1cb477b006581974e95bc263179 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <netinet/ether.h>
+#include <netinet/in.h>
 #include <linux/if.h>
 
 #include "network-internal.h"
index e2d77e9ad2c56f24892733d66929004a20caad11..863171b0712ffcff041de28f629dc3e51b8625ad 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <netinet/ether.h>
+#include <netinet/in.h>
 #include <linux/if.h>
 #include <unistd.h>
 
index 4db9f3f980ff4d46a025956f1ccb091dcf3c4cb1..2b6ff2b6c58d6e1e4f5e97d607934e7317ab6394 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <netinet/ether.h>
+#include <netinet/in.h>
 #include <linux/if.h>
 #include <linux/can/netlink.h>
 #include <unistd.h>
@@ -1136,20 +1136,20 @@ static int link_push_uplink_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) {
         log_debug("Copying NTP server information from %s", link->ifname);
 
         STRV_FOREACH(a, link->network->ntp) {
-                struct in_addr ia;
+                union in_addr_union ia;
 
                 /* Only look for IPv4 addresses */
-                if (inet_pton(AF_INET, *a, &ia) <= 0)
+                if (in_addr_from_string(AF_INET, *a, &ia) <= 0)
                         continue;
 
                 /* Never propagate obviously borked data */
-                if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia))
+                if (in4_addr_is_null(&ia.in) || in4_addr_is_localhost(&ia.in))
                         continue;
 
                 if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1))
                         return log_oom();
 
-                addresses[n_addresses++] = ia;
+                addresses[n_addresses++] = ia.in;
         }
 
         if (link->network->dhcp_use_ntp && link->dhcp_lease) {
@@ -1592,7 +1592,6 @@ static int link_set_bridge(Link *link) {
                 r = sd_netlink_message_append_u8(req, IFLA_BRPORT_PROTECT, link->network->allow_port_to_be_root);
                 if (r < 0)
                         return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PROTECT attribute: %m");
-
         }
 
         if (link->network->unicast_flood >= 0) {
@@ -1625,6 +1624,18 @@ static int link_set_bridge(Link *link) {
                         return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_LEARNING attribute: %m");
         }
 
+        if (link->network->bridge_proxy_arp >= 0) {
+                r = sd_netlink_message_append_u8(req, IFLA_BRPORT_PROXYARP, link->network->bridge_proxy_arp);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PROXYARP attribute: %m");
+        }
+
+        if (link->network->bridge_proxy_arp_wifi >= 0) {
+                r = sd_netlink_message_append_u8(req, IFLA_BRPORT_PROXYARP_WIFI, link->network->bridge_proxy_arp_wifi);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PROXYARP_WIFI attribute: %m");
+        }
+
         if (link->network->cost != 0) {
                 r = sd_netlink_message_append_u32(req, IFLA_BRPORT_COST, link->network->cost);
                 if (r < 0)
@@ -1637,6 +1648,12 @@ static int link_set_bridge(Link *link) {
                         return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PRIORITY attribute: %m");
         }
 
+        if (link->network->multicast_router != _MULTICAST_ROUTER_INVALID) {
+                r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MULTICAST_ROUTER, link->network->multicast_router);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MULTICAST_ROUTER attribute: %m");
+        }
+
         r = sd_netlink_message_close_container(req);
         if (r < 0)
                 return log_link_error_errno(link, r, "Could not append IFLA_LINKINFO attribute: %m");
@@ -3027,10 +3044,14 @@ static int link_update_lldp(Link *link) {
                 r = sd_lldp_start(link->lldp);
                 if (r > 0)
                         log_link_debug(link, "Started LLDP.");
+                else
+                        log_link_warning_errno(link, r, "Failed to start LLDP: %m");
         } else {
                 r = sd_lldp_stop(link->lldp);
                 if (r > 0)
                         log_link_debug(link, "Stopped LLDP.");
+                else
+                        log_link_warning_errno(link, r, "Failed to stop LLDP: %m");
         }
 
         return r;
index a842d131a53f809861e690a034855234d41a207c..1af25dc812c6275acfbaedfe46987348df5befd0 100644 (file)
@@ -1,9 +1,10 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
+#include <netinet/in.h>
 #include <sys/socket.h>
+#include <unistd.h>
 #include <linux/if.h>
 #include <linux/fib_rules.h>
-#include <unistd.h>
 
 #include "sd-daemon.h"
 #include "sd-netlink.h"
@@ -477,19 +478,17 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo
 }
 
 int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) {
+        _cleanup_free_ char *buf = NULL;
         Manager *m = userdata;
         Link *link = NULL;
         uint16_t type;
-        unsigned char flags;
-        int family;
-        unsigned char prefixlen;
-        unsigned char scope;
+        unsigned char flags, prefixlen, scope;
         union in_addr_union in_addr = IN_ADDR_NULL;
         struct ifa_cacheinfo cinfo;
         Address *address = NULL;
-        char buf[INET6_ADDRSTRLEN], valid_buf[FORMAT_TIMESPAN_MAX];
+        char valid_buf[FORMAT_TIMESPAN_MAX];
         const char *valid_str = NULL;
-        int r, ifindex;
+        int ifindex, family, r;
 
         assert(rtnl);
         assert(message);
@@ -577,8 +576,9 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
                 assert_not_reached("Received unsupported address family");
         }
 
-        if (!inet_ntop(family, &in_addr, buf, INET6_ADDRSTRLEN)) {
-                log_link_warning(link, "Could not print address, ignoring");
+        r = in_addr_to_string(family, &in_addr, &buf);
+        if (r < 0) {
+                log_link_warning_errno(link, r, "Could not print address, ignoring: %m");
                 return 0;
         }
 
@@ -586,12 +586,10 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
         if (r < 0 && r != -ENODATA) {
                 log_link_warning_errno(link, r, "rtnl: cannot get IFA_CACHEINFO attribute, ignoring: %m");
                 return 0;
-        } else if (r >= 0) {
-                if (cinfo.ifa_valid != CACHE_INFO_INFINITY_LIFE_TIME)
-                        valid_str = format_timespan(valid_buf, FORMAT_TIMESPAN_MAX,
-                                                    cinfo.ifa_valid * USEC_PER_SEC,
-                                                    USEC_PER_SEC);
-        }
+        } else if (r >= 0 && cinfo.ifa_valid != CACHE_INFO_INFINITY_LIFE_TIME)
+                valid_str = format_timespan(valid_buf, FORMAT_TIMESPAN_MAX,
+                                            cinfo.ifa_valid * USEC_PER_SEC,
+                                            USEC_PER_SEC);
 
         (void) address_get(link, family, &in_addr, prefixlen, &address);
 
index 1084a3a60b6301aef1fd6dbdae8b74075a8a6558..06fa9d8d32f7ffd66b7f0c301538798f3126403d 100644 (file)
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-#include <arpa/inet.h>
-
 #include "sd-bus.h"
 #include "sd-device.h"
 #include "sd-event.h"
index 3b546528055b2476ec72457cdaae074576acd020..325f7ae59294378c3ea2fdb920f8f58400793527 100644 (file)
@@ -66,12 +66,11 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
                 if (address->family != AF_INET6)
                         continue;
                 if (in_addr_equal(AF_INET6, &gateway, &address->in_addr)) {
-                        char buffer[INET6_ADDRSTRLEN];
+                        _cleanup_free_ char *buffer = NULL;
 
+                        (void) in_addr_to_string(AF_INET6, &address->in_addr, &buffer);
                         log_link_debug(link, "No NDisc route added, gateway %s matches local address",
-                                       inet_ntop(AF_INET6,
-                                                 &address->in_addr.in6,
-                                                 buffer, sizeof(buffer)));
+                                       strnull(buffer));
                         return 0;
                 }
         }
@@ -80,12 +79,11 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
                 if (address->family != AF_INET6)
                         continue;
                 if (in_addr_equal(AF_INET6, &gateway, &address->in_addr)) {
-                        char buffer[INET6_ADDRSTRLEN];
+                        _cleanup_free_ char *buffer = NULL;
 
+                        (void) in_addr_to_string(AF_INET6, &address->in_addr, &buffer);
                         log_link_debug(link, "No NDisc route added, gateway %s matches local address",
-                                       inet_ntop(AF_INET6,
-                                                 &address->in_addr.in6,
-                                                 buffer, sizeof(buffer)));
+                                       strnull(buffer));
                         return 0;
                 }
         }
index 98c2241fca2ad79c2c74d90468e737e720629448..c6036e29faf6819f1912889a7c972656ae3593ac 100644 (file)
@@ -150,6 +150,7 @@ DHCP.UseTimezone,                       config_parse_bool,
 DHCP.IAID,                              config_parse_iaid,                               0,                             0
 DHCP.ListenPort,                        config_parse_uint16,                             0,                             offsetof(Network, dhcp_client_port)
 DHCP.RapidCommit,                       config_parse_bool,                               0,                             offsetof(Network, rapid_commit)
+DHCP.BlackList,                         config_parse_dhcp_black_listed_ip_address,       0,                             0
 DHCP.ForceDHCPv6PDOtherInformation,     config_parse_bool,                               0,                             offsetof(Network, dhcp6_force_pd_other_information)
 IPv6AcceptRA.UseAutonomousPrefix,       config_parse_bool,                               0,                             offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
 IPv6AcceptRA.UseOnLinkPrefix,           config_parse_bool,                               0,                             offsetof(Network, ipv6_accept_ra_use_onlink_prefix)
@@ -177,7 +178,10 @@ Bridge.MulticastFlood,                  config_parse_tristate,
 Bridge.MulticastToUnicast,              config_parse_tristate,                           0,                             offsetof(Network, multicast_to_unicast)
 Bridge.NeighborSuppression,             config_parse_tristate,                           0,                             offsetof(Network, neighbor_suppression)
 Bridge.Learning,                        config_parse_tristate,                           0,                             offsetof(Network, learning)
+Bridge.ProxyARP,                        config_parse_tristate,                           0,                             offsetof(Network, bridge_proxy_arp)
+Bridge.ProxyARPWiFi,                    config_parse_tristate,                           0,                             offsetof(Network, bridge_proxy_arp_wifi)
 Bridge.Priority,                        config_parse_bridge_port_priority,               0,                             offsetof(Network, priority)
+Bridge.MulticastRouter,                 config_parse_multicast_router,                   0,                             offsetof(Network, multicast_router)
 BridgeFDB.MACAddress,                   config_parse_fdb_hwaddr,                         0,                             0
 BridgeFDB.VLANId,                       config_parse_fdb_vlan_id,                        0,                             0
 BridgeFDB.Destination,                  config_parse_fdb_destination,                    0,                             0
index 6e0e1eae08dfd19c3fa4f73320abe2c9b18d6dbc..d39aaa207bd3804e3793a42f57c8ecb3e40b68b8 100644 (file)
@@ -1,5 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
+#include <net/if.h>
+#include <netinet/in.h>
 #include <linux/netdevice.h>
 
 #include "alloc-util.h"
@@ -386,7 +388,10 @@ int network_load_one(Manager *manager, const char *filename) {
                 .multicast_to_unicast = -1,
                 .neighbor_suppression = -1,
                 .learning = -1,
+                .bridge_proxy_arp = -1,
+                .bridge_proxy_arp_wifi = -1,
                 .priority = LINK_BRIDGE_PORT_PRIORITY_INVALID,
+                .multicast_router = _MULTICAST_ROUTER_INVALID,
 
                 .lldp_mode = LLDP_MODE_ROUTERS_ONLY,
 
@@ -512,7 +517,7 @@ static Network *network_free(Network *network) {
         free(network->dhcp_vendor_class_identifier);
         strv_free(network->dhcp_user_class);
         free(network->dhcp_hostname);
-
+        set_free(network->dhcp_black_listed_ip);
         free(network->mac);
 
         strv_free(network->ntp);
@@ -1115,7 +1120,8 @@ int config_parse_dhcp_server_dns(
 
         for (;;) {
                 _cleanup_free_ char *w = NULL;
-                struct in_addr a, *m;
+                union in_addr_union a;
+                struct in_addr *m;
 
                 r = extract_first_word(&p, &w, NULL, 0);
                 if (r == -ENOMEM)
@@ -1128,9 +1134,10 @@ int config_parse_dhcp_server_dns(
                 if (r == 0)
                         break;
 
-                if (inet_pton(AF_INET, w, &a) <= 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0,
-                                   "Failed to parse DNS server address, ignoring: %s", w);
+                r = in_addr_from_string(AF_INET, w, &a);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to parse DNS server address '%s', ignoring assignment: %m", w);
                         continue;
                 }
 
@@ -1138,7 +1145,7 @@ int config_parse_dhcp_server_dns(
                 if (!m)
                         return log_oom();
 
-                m[n->n_dhcp_server_dns++] = a;
+                m[n->n_dhcp_server_dns++] = a.in;
                 n->dhcp_server_dns = m;
         }
 
@@ -1275,7 +1282,8 @@ int config_parse_dhcp_server_ntp(
 
         for (;;) {
                 _cleanup_free_ char *w = NULL;
-                struct in_addr a, *m;
+                union in_addr_union a;
+                struct in_addr *m;
 
                 r = extract_first_word(&p, &w, NULL, 0);
                 if (r == -ENOMEM)
@@ -1288,9 +1296,10 @@ int config_parse_dhcp_server_ntp(
                 if (r == 0)
                         return 0;
 
-                if (inet_pton(AF_INET, w, &a) <= 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0,
-                                   "Failed to parse NTP server address, ignoring: %s", w);
+                r = in_addr_from_string(AF_INET, w, &a);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to parse NTP server address '%s', ignoring: %m", w);
                         continue;
                 }
 
@@ -1298,7 +1307,7 @@ int config_parse_dhcp_server_ntp(
                 if (!m)
                         return log_oom();
 
-                m[n->n_dhcp_server_ntp++] = a;
+                m[n->n_dhcp_server_ntp++] = a.in;
                 n->dhcp_server_ntp = m;
         }
 }
@@ -1620,6 +1629,71 @@ int config_parse_dhcp_max_attempts(
         return 0;
 }
 
+int config_parse_dhcp_black_listed_ip_address(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Network *network = data;
+        const char *p;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (isempty(rvalue)) {
+                network->dhcp_black_listed_ip = set_free(network->dhcp_black_listed_ip);
+                return 0;
+        }
+
+        for (p = rvalue;;) {
+                _cleanup_free_ char *n = NULL;
+                union in_addr_union ip;
+
+                r = extract_first_word(&p, &n, NULL, 0);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to parse DHCP black listed ip address, ignoring assignment: %s",
+                                   rvalue);
+                        return 0;
+                }
+                if (r == 0)
+                        return 0;
+
+                r = in_addr_from_string(AF_INET, n, &ip);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "DHCP black listed ip address is invalid, ignoring assignment: %s", n);
+                        continue;
+                }
+
+                r = set_ensure_allocated(&network->dhcp_black_listed_ip, NULL);
+                if (r < 0)
+                        return log_oom();
+
+                r = set_put(network->dhcp_black_listed_ip, UINT32_TO_PTR(ip.in.s_addr));
+                if (r == -EEXIST) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                                   "DHCP black listed ip address is duplicated, ignoring assignment: %s", n);
+                        continue;
+                }
+                if (r < 0)
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to store DHCP black listed ip address '%s', ignoring assignment: %m", n);
+        }
+
+        return 0;
+}
+
 DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_use_domains, dhcp_use_domains, DHCPUseDomains,
                          "Failed to parse DHCP use domains setting");
 
@@ -1715,3 +1789,14 @@ int config_parse_required_for_online(
 
         return 0;
 }
+
+static const char* const multicast_router_table[_MULTICAST_ROUTER_MAX] = {
+        [MULTICAST_ROUTER_NONE] = "no",
+        [MULTICAST_ROUTER_TEMPORARY_QUERY] = "query",
+        [MULTICAST_ROUTER_PERMANENT] = "permanent",
+        [MULTICAST_ROUTER_TEMPORARY] = "temporary",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(multicast_router, MulticastRouter, _MULTICAST_ROUTER_INVALID);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_multicast_router, multicast_router, MulticastRouter,
+                         "Failed to parse bridge multicast router setting");
index 9ee8fb72b8d73d52a606f1490cde96559db95cca..366f9d3f3941abf7e8d990e9c239e7f0911f7682 100644 (file)
@@ -1,6 +1,9 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+
 #include "sd-bus.h"
 #include "sd-device.h"
 
@@ -84,6 +87,15 @@ typedef enum RADVPrefixDelegation {
         _RADV_PREFIX_DELEGATION_INVALID = -1,
 } RADVPrefixDelegation;
 
+typedef enum MulticastRouter {
+        MULTICAST_ROUTER_NONE            = MDB_RTR_TYPE_DISABLED,
+        MULTICAST_ROUTER_TEMPORARY_QUERY = MDB_RTR_TYPE_TEMP_QUERY,
+        MULTICAST_ROUTER_PERMANENT       = MDB_RTR_TYPE_PERM,
+        MULTICAST_ROUTER_TEMPORARY       = MDB_RTR_TYPE_TEMP,
+        _MULTICAST_ROUTER_MAX,
+        _MULTICAST_ROUTER_INVALID = -1,
+} MulticastRouter;
+
 typedef struct Manager Manager;
 
 struct Network {
@@ -135,6 +147,7 @@ struct Network {
         bool dhcp_use_hostname;
         bool dhcp_route_table_set;
         DHCPUseDomains dhcp_use_domains;
+        Set *dhcp_black_listed_ip;
 
         /* DHCP Server Support */
         bool dhcp_server;
@@ -181,8 +194,11 @@ struct Network {
         int multicast_to_unicast;
         int neighbor_suppression;
         int learning;
+        int bridge_proxy_arp;
+        int bridge_proxy_arp_wifi;
         uint32_t cost;
         uint16_t priority;
+        MulticastRouter multicast_router;
 
         bool use_br_vlan;
         uint16_t pvid;
@@ -305,6 +321,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_privacy_extensions);
 CONFIG_PARSER_PROTOTYPE(config_parse_hostname);
 CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_dns);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_black_listed_ip_address);
 CONFIG_PARSER_PROTOTYPE(config_parse_radv_dns);
 CONFIG_PARSER_PROTOTYPE(config_parse_radv_search_domains);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_ntp);
@@ -317,6 +334,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_ntp);
 CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
 CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_max_attempts);
+CONFIG_PARSER_PROTOTYPE(config_parse_multicast_router);
 /* Legacy IPv4LL support */
 CONFIG_PARSER_PROTOTYPE(config_parse_ipv4ll);
 
@@ -338,3 +356,6 @@ LLDPMode lldp_mode_from_string(const char *s) _pure_;
 
 const char* radv_prefix_delegation_to_string(RADVPrefixDelegation i) _const_;
 RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_;
+
+const char* multicast_router_to_string(MulticastRouter i) _const_;
+MulticastRouter multicast_router_from_string(const char *s) _pure_;
index 28699ba2369d9f070df9fe3b90196e475df6d16a..4ee0b5489ef7002c5ae3fa90bb5de9c53898e8ad 100644 (file)
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <inttypes.h>
+#include <netinet/in.h>
 #include <linux/fib_rules.h>
 #include <stdbool.h>
 
index 4b04aa0463c91c862470ad2411d1db433e50e088..090f3777a5ae2de7304c05f25c0e8119b018bf3d 100644 (file)
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
+#include <netinet/in.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
index 21ee97e84ea9d211051ecad6a34dae6d9cb57c12..23fcea666e6eeeb73dd994073c35b8114ff33c82 100644 (file)
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
+#include <arpa/inet.h>
 #include <sys/param.h>
 
 #include "sd-device.h"
 static void test_deserialize_in_addr(void) {
         _cleanup_free_ struct in_addr *addresses = NULL;
         _cleanup_free_ struct in6_addr *addresses6 = NULL;
-        struct in_addr  a, b, c;
-        struct in6_addr d, e, f;
+        union in_addr_union a, b, c, d, e, f;
         int size;
         const char *addresses_string = "192.168.0.1 0:0:0:0:0:FFFF:204.152.189.116 192.168.0.2 ::1 192.168.0.3 1:0:0:0:0:0:0:8";
 
-        assert_se(inet_pton(AF_INET, "0:0:0:0:0:FFFF:204.152.189.116", &a) == 0);
-        assert_se(inet_pton(AF_INET6, "192.168.0.1", &d) == 0);
+        assert_se(in_addr_from_string(AF_INET, "0:0:0:0:0:FFFF:204.152.189.116", &a) < 0);
+        assert_se(in_addr_from_string(AF_INET6, "192.168.0.1", &d) < 0);
 
-        assert_se(inet_pton(AF_INET, "192.168.0.1", &a) == 1);
-        assert_se(inet_pton(AF_INET, "192.168.0.2", &b) == 1);
-        assert_se(inet_pton(AF_INET, "192.168.0.3", &c) == 1);
-        assert_se(inet_pton(AF_INET6, "0:0:0:0:0:FFFF:204.152.189.116", &d) == 1);
-        assert_se(inet_pton(AF_INET6, "::1", &e) == 1);
-        assert_se(inet_pton(AF_INET6, "1:0:0:0:0:0:0:8", &f) == 1);
+        assert_se(in_addr_from_string(AF_INET, "192.168.0.1", &a) >= 0);
+        assert_se(in_addr_from_string(AF_INET, "192.168.0.2", &b) >= 0);
+        assert_se(in_addr_from_string(AF_INET, "192.168.0.3", &c) >= 0);
+        assert_se(in_addr_from_string(AF_INET6, "0:0:0:0:0:FFFF:204.152.189.116", &d) >= 0);
+        assert_se(in_addr_from_string(AF_INET6, "::1", &e) >= 0);
+        assert_se(in_addr_from_string(AF_INET6, "1:0:0:0:0:0:0:8", &f) >= 0);
 
         assert_se((size = deserialize_in_addrs(&addresses, addresses_string)) >= 0);
         assert_se(size == 3);
-        assert_se(!memcmp(&a, &addresses[0], sizeof(struct in_addr)));
-        assert_se(!memcmp(&b, &addresses[1], sizeof(struct in_addr)));
-        assert_se(!memcmp(&c, &addresses[2], sizeof(struct in_addr)));
+        assert_se(in_addr_equal(AF_INET, &a, (union in_addr_union *) &addresses[0]));
+        assert_se(in_addr_equal(AF_INET, &b, (union in_addr_union *) &addresses[1]));
+        assert_se(in_addr_equal(AF_INET, &c, (union in_addr_union *) &addresses[2]));
 
         assert_se((size = deserialize_in6_addrs(&addresses6, addresses_string)) >= 0);
         assert_se(size == 3);
-        assert_se(!memcmp(&d, &addresses6[0], sizeof(struct in6_addr)));
-        assert_se(!memcmp(&e, &addresses6[1], sizeof(struct in6_addr)));
-        assert_se(!memcmp(&f, &addresses6[2], sizeof(struct in6_addr)));
+        assert_se(in_addr_equal(AF_INET6, &d, (union in_addr_union *) &addresses6[0]));
+        assert_se(in_addr_equal(AF_INET6, &e, (union in_addr_union *) &addresses6[1]));
+        assert_se(in_addr_equal(AF_INET6, &f, (union in_addr_union *) &addresses6[2]));
 }
 
 static void test_deserialize_dhcp_routes(void) {
@@ -146,13 +146,13 @@ static void test_address_equality(void) {
         a2->family = AF_INET;
         assert_se(address_equal(a1, a2));
 
-        assert_se(inet_pton(AF_INET, "192.168.3.9", &a1->in_addr.in));
+        assert_se(in_addr_from_string(AF_INET, "192.168.3.9", &a1->in_addr) >= 0);
         assert_se(!address_equal(a1, a2));
-        assert_se(inet_pton(AF_INET, "192.168.3.9", &a2->in_addr.in));
+        assert_se(in_addr_from_string(AF_INET, "192.168.3.9", &a2->in_addr) >= 0);
         assert_se(address_equal(a1, a2));
-        assert_se(inet_pton(AF_INET, "192.168.3.10", &a1->in_addr_peer.in));
+        assert_se(in_addr_from_string(AF_INET, "192.168.3.10", &a1->in_addr_peer) >= 0);
         assert_se(address_equal(a1, a2));
-        assert_se(inet_pton(AF_INET, "192.168.3.11", &a2->in_addr_peer.in));
+        assert_se(in_addr_from_string(AF_INET, "192.168.3.11", &a2->in_addr_peer) >= 0);
         assert_se(address_equal(a1, a2));
         a1->prefixlen = 10;
         assert_se(!address_equal(a1, a2));
@@ -163,14 +163,14 @@ static void test_address_equality(void) {
         assert_se(!address_equal(a1, a2));
 
         a2->family = AF_INET6;
-        assert_se(inet_pton(AF_INET6, "2001:4ca0:4f01::2", &a1->in_addr.in6));
-        assert_se(inet_pton(AF_INET6, "2001:4ca0:4f01::2", &a2->in_addr.in6));
+        assert_se(in_addr_from_string(AF_INET6, "2001:4ca0:4f01::2", &a1->in_addr) >= 0);
+        assert_se(in_addr_from_string(AF_INET6, "2001:4ca0:4f01::2", &a2->in_addr) >= 0);
         assert_se(address_equal(a1, a2));
 
         a2->prefixlen = 8;
         assert_se(address_equal(a1, a2));
 
-        assert_se(inet_pton(AF_INET6, "2001:4ca0:4f01::1", &a2->in_addr.in6));
+        assert_se(in_addr_from_string(AF_INET6, "2001:4ca0:4f01::1", &a2->in_addr) >= 0);
         assert_se(!address_equal(a1, a2));
 }
 
index c80a67cdde45819decc52623a0cf839c767ac667..aa0423ccad73af5c70c941196ae65676d36031fc 100644 (file)
@@ -164,6 +164,8 @@ shared_sources = files('''
         uid-range.c
         uid-range.h
         utmp-wtmp.h
+        varlink.c
+        varlink.h
         verbs.c
         verbs.h
         vlan-util.c
diff --git a/src/shared/varlink.c b/src/shared/varlink.c
new file mode 100644 (file)
index 0000000..5c5f507
--- /dev/null
@@ -0,0 +1,2388 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <sys/poll.h>
+
+#include "alloc-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "hashmap.h"
+#include "list.h"
+#include "process-util.h"
+#include "set.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "umask-util.h"
+#include "user-util.h"
+#include "varlink.h"
+
+#define VARLINK_DEFAULT_CONNECTIONS_MAX 4096U
+#define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 1024U
+
+#define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC)
+#define VARLINK_BUFFER_MAX (16U*1024U*1024U)
+#define VARLINK_READ_SIZE (64U*1024U)
+
+typedef enum VarlinkState {
+        /* Client side states */
+        VARLINK_IDLE_CLIENT,
+        VARLINK_AWAITING_REPLY,
+        VARLINK_CALLING,
+        VARLINK_CALLED,
+        VARLINK_PROCESSING_REPLY,
+
+        /* Server side states */
+        VARLINK_IDLE_SERVER,
+        VARLINK_PROCESSING_METHOD,
+        VARLINK_PROCESSING_METHOD_MORE,
+        VARLINK_PROCESSING_METHOD_ONEWAY,
+        VARLINK_PROCESSED_METHOD,
+        VARLINK_PROCESSED_METHOD_MORE,
+        VARLINK_PENDING_METHOD,
+        VARLINK_PENDING_METHOD_MORE,
+
+        /* Common states (only during shutdown) */
+        VARLINK_PENDING_DISCONNECT,
+        VARLINK_PENDING_TIMEOUT,
+        VARLINK_PROCESSING_DISCONNECT,
+        VARLINK_PROCESSING_TIMEOUT,
+        VARLINK_PROCESSING_FAILURE,
+        VARLINK_DISCONNECTED,
+
+        _VARLINK_STATE_MAX,
+        _VARLINK_STATE_INVALID = -1
+} VarlinkState;
+
+/* Tests whether we are not yet disconnected. Note that this is true during all states where the connection
+ * is still good for something, and false only when it's dead for good. This means: when we are
+ * asynchronously connecting to a peer and the connect() is still pending, then this will return 'true', as
+ * the connection is still good, and we are likely to be able to properly operate on it soon. */
+#define VARLINK_STATE_IS_ALIVE(state)                   \
+        IN_SET(state,                                   \
+               VARLINK_IDLE_CLIENT,                     \
+               VARLINK_AWAITING_REPLY,                  \
+               VARLINK_CALLING,                         \
+               VARLINK_CALLED,                          \
+               VARLINK_PROCESSING_REPLY,                \
+               VARLINK_IDLE_SERVER,                     \
+               VARLINK_PROCESSING_METHOD,               \
+               VARLINK_PROCESSING_METHOD_MORE,          \
+               VARLINK_PROCESSING_METHOD_ONEWAY,        \
+               VARLINK_PROCESSED_METHOD,                \
+               VARLINK_PROCESSED_METHOD_MORE,           \
+               VARLINK_PENDING_METHOD,                  \
+               VARLINK_PENDING_METHOD_MORE)
+
+struct Varlink {
+        unsigned n_ref;
+
+        VarlinkServer *server;
+
+        VarlinkState state;
+        bool connecting; /* This boolean indicates whether the socket fd we are operating on is currently
+                          * processing an asynchronous connect(). In that state we watch the socket for
+                          * EPOLLOUT, but we refrain from calling read() or write() on the socket as that
+                          * will trigger ENOTCONN. Note that this boolean is kept separate from the
+                          * VarlinkState above on purpose: while the connect() is still not complete we
+                          * already want to allow queuing of messages and similar. Thus it's nice to keep
+                          * these two state concepts separate: the VarlinkState encodes what our own view of
+                          * the connection is, i.e. whether we think it's a server, a client, and has
+                          * something queued already, while 'connecting' tells us a detail about the
+                          * transport used below, that should have no effect on how we otherwise accept and
+                          * process operations from the user.
+                          *
+                          * Or to say this differently: VARLINK_STATE_IS_ALIVE(state) tells you whether the
+                          * connection is good to use, even if it might not be fully connected
+                          * yet. connecting=true then informs you that actually we are still connecting, and
+                          * the connection is actually not established yet and thus any requests you enqueue
+                          * now will still work fine but will be queued only, not sent yet, but that
+                          * shouldn't stop you from using the connection, since eventually whatever you queue
+                          * *will* be sent.
+                          *
+                          * Or to say this even differently: 'state' is a high-level ("application layer"
+                          * high, if you so will) state, while 'conecting' is a low-level ("transport layer"
+                          * low, if you so will) state, and while they are not entirely unrelated and
+                          * sometimes propagate effects to each other they are only asynchronously connected
+                          * at most. */
+        unsigned n_pending;
+
+        int fd;
+
+        char *input_buffer; /* valid data starts at input_buffer_index, ends at input_buffer_index+input_buffer_size */
+        size_t input_buffer_allocated;
+        size_t input_buffer_index;
+        size_t input_buffer_size;
+        size_t input_buffer_unscanned;
+
+        char *output_buffer; /* valid data starts at output_buffer_index, ends at output_buffer_index+output_buffer_size */
+        size_t output_buffer_allocated;
+        size_t output_buffer_index;
+        size_t output_buffer_size;
+
+        VarlinkReply reply_callback;
+
+        JsonVariant *current;
+        JsonVariant *reply;
+
+        struct ucred ucred;
+        bool ucred_acquired:1;
+
+        bool write_disconnected:1;
+        bool read_disconnected:1;
+        bool prefer_read_write:1;
+        bool got_pollhup:1;
+
+        usec_t timestamp;
+        usec_t timeout;
+
+        void *userdata;
+        char *description;
+
+        sd_event *event;
+        sd_event_source *io_event_source;
+        sd_event_source *time_event_source;
+        sd_event_source *quit_event_source;
+        sd_event_source *defer_event_source;
+};
+
+typedef struct VarlinkServerSocket VarlinkServerSocket;
+
+struct VarlinkServerSocket {
+        VarlinkServer *server;
+
+        int fd;
+        char *address;
+
+        sd_event_source *event_source;
+
+        LIST_FIELDS(VarlinkServerSocket, sockets);
+};
+
+struct VarlinkServer {
+        unsigned n_ref;
+        VarlinkServerFlags flags;
+
+        LIST_HEAD(VarlinkServerSocket, sockets);
+
+        Hashmap *methods;
+        VarlinkConnect connect_callback;
+
+        sd_event *event;
+        int64_t event_priority;
+
+        unsigned n_connections;
+        Hashmap *by_uid;
+
+        void *userdata;
+        char *description;
+
+        unsigned connections_max;
+        unsigned connections_per_uid_max;
+};
+
+static const char* const varlink_state_table[_VARLINK_STATE_MAX] = {
+        [VARLINK_IDLE_CLIENT]              = "idle-client",
+        [VARLINK_AWAITING_REPLY]           = "awaiting-reply",
+        [VARLINK_CALLING]                  = "calling",
+        [VARLINK_CALLED]                   = "called",
+        [VARLINK_PROCESSING_REPLY]         = "processing-reply",
+        [VARLINK_IDLE_SERVER]              = "idle-server",
+        [VARLINK_PROCESSING_METHOD]        = "processing-method",
+        [VARLINK_PROCESSING_METHOD_MORE]   = "processing-method-more",
+        [VARLINK_PROCESSING_METHOD_ONEWAY] = "processing-method-oneway",
+        [VARLINK_PROCESSED_METHOD]         = "processed-method",
+        [VARLINK_PROCESSED_METHOD_MORE]    = "processed-method-more",
+        [VARLINK_PENDING_METHOD]           = "pending-method",
+        [VARLINK_PENDING_METHOD_MORE]      = "pending-method-more",
+        [VARLINK_PENDING_DISCONNECT]       = "pending-disconnect",
+        [VARLINK_PENDING_TIMEOUT]          = "pending-timeout",
+        [VARLINK_PROCESSING_DISCONNECT]    = "processing-disconnect",
+        [VARLINK_PROCESSING_TIMEOUT]       = "processing-timeout",
+        [VARLINK_PROCESSING_FAILURE]       = "processing-failure",
+        [VARLINK_DISCONNECTED]             = "disconnected",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(varlink_state, VarlinkState);
+
+#define varlink_log_errno(v, error, fmt, ...)                           \
+        log_debug_errno(error, "%s: " fmt, varlink_description(v), ##__VA_ARGS__)
+
+#define varlink_log(v, fmt, ...)                                        \
+        log_debug("%s: " fmt, varlink_description(v), ##__VA_ARGS__)
+
+#define varlink_server_log_errno(s, error, fmt, ...) \
+        log_debug_errno(error, "%s: " fmt, varlink_server_description(s), ##__VA_ARGS__)
+
+#define varlink_server_log(s, fmt, ...) \
+        log_debug("%s: " fmt, varlink_server_description(s), ##__VA_ARGS__)
+
+static inline const char *varlink_description(Varlink *v) {
+        return strna(v ? v->description : NULL);
+}
+
+static inline const char *varlink_server_description(VarlinkServer *s) {
+        return strna(s ? s->description : NULL);
+}
+
+static void varlink_set_state(Varlink *v, VarlinkState state) {
+        assert(v);
+        assert(state >= 0 && state < _VARLINK_STATE_MAX);
+
+        if (v->state < 0)
+                varlink_log(v, "varlink: setting state %s",
+                            varlink_state_to_string(state));
+        else
+                varlink_log(v, "varlink: changing state %s â†’ %s",
+                            varlink_state_to_string(v->state),
+                            varlink_state_to_string(state));
+
+        v->state = state;
+}
+
+static int varlink_new(Varlink **ret) {
+        Varlink *v;
+
+        assert(ret);
+
+        v = new(Varlink, 1);
+        if (!v)
+                return -ENOMEM;
+
+        *v = (Varlink) {
+                .n_ref = 1,
+                .fd = -1,
+
+                .state = _VARLINK_STATE_INVALID,
+
+                .ucred.uid = UID_INVALID,
+                .ucred.gid = GID_INVALID,
+
+                .timestamp = USEC_INFINITY,
+                .timeout = VARLINK_DEFAULT_TIMEOUT_USEC
+        };
+
+        *ret = v;
+        return 0;
+}
+
+int varlink_connect_address(Varlink **ret, const char *address) {
+        _cleanup_(varlink_unrefp) Varlink *v = NULL;
+        union sockaddr_union sockaddr;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(address, -EINVAL);
+
+        r = sockaddr_un_set_path(&sockaddr.un, address);
+        if (r < 0)
+                return r;
+
+        r = varlink_new(&v);
+        if (r < 0)
+                return r;
+
+        v->fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+        if (v->fd < 0)
+                return -errno;
+
+        if (connect(v->fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0) {
+                if (!IN_SET(errno, EAGAIN, EINPROGRESS))
+                        return -errno;
+
+                v->connecting = true; /* We are asynchronously connecting, i.e. the connect() is being
+                                       * processed in the background. As long as that's the case the socket
+                                       * is in a special state: it's there, we can poll it for EPOLLOUT, but
+                                       * if we attempt to write() to it before we see EPOLLOUT we'll get
+                                       * ENOTCONN (and not EAGAIN, like we would for a normal connected
+                                       * socket that isn't writable at the moment). Since ENOTCONN on write()
+                                       * hence can mean two different things (i.e. connection not complete
+                                       * yet vs. already disconnected again), we store as a boolean whether
+                                       * we are still in connect(). */
+        }
+
+        varlink_set_state(v, VARLINK_IDLE_CLIENT);
+
+        *ret = TAKE_PTR(v);
+        return r;
+}
+
+int varlink_connect_fd(Varlink **ret, int fd) {
+        Varlink *v;
+        int r;
+
+        assert_return(ret, -EINVAL);
+        assert_return(fd >= 0, -EBADF);
+
+        r = fd_nonblock(fd, true);
+        if (r < 0)
+                return r;
+
+        r = varlink_new(&v);
+        if (r < 0)
+                return r;
+
+        v->fd = fd;
+        varlink_set_state(v, VARLINK_IDLE_CLIENT);
+
+        /* Note that if this function is called we assume the passed socket (if it is one) is already
+         * properly connected, i.e. any asynchronous connect() done on it already completed. Because of that
+         * we'll not set the 'connecting' boolean here, i.e. we don't need to avoid write()ing to the socket
+         * until the connection is fully set up. Behaviour here is hence a bit different from
+         * varlink_connect_address() above, as there we do handle asynchronous connections ourselves and
+         * avoid doing write() on it before we saw EPOLLOUT for the first time. */
+
+        *ret = v;
+        return 0;
+}
+
+static void varlink_detach_event_sources(Varlink *v) {
+        assert(v);
+
+        v->io_event_source = sd_event_source_disable_unref(v->io_event_source);
+
+        v->time_event_source = sd_event_source_disable_unref(v->time_event_source);
+
+        v->quit_event_source = sd_event_source_disable_unref(v->quit_event_source);
+
+        v->defer_event_source = sd_event_source_disable_unref(v->defer_event_source);
+}
+
+static void varlink_clear(Varlink *v) {
+        assert(v);
+
+        varlink_detach_event_sources(v);
+
+        v->fd = safe_close(v->fd);
+
+        v->input_buffer = mfree(v->input_buffer);
+        v->output_buffer = mfree(v->output_buffer);
+
+        v->current = json_variant_unref(v->current);
+        v->reply = json_variant_unref(v->reply);
+
+        v->event = sd_event_unref(v->event);
+}
+
+static Varlink* varlink_destroy(Varlink *v) {
+        if (!v)
+                return NULL;
+
+        /* If this is called the server object must already been unreffed here. Why that? because when we
+         * linked up the varlink connection with the server object we took one ref in each direction */
+        assert(!v->server);
+
+        varlink_clear(v);
+
+        free(v->description);
+        return mfree(v);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(Varlink, varlink, varlink_destroy);
+
+static int varlink_test_disconnect(Varlink *v) {
+        assert(v);
+
+        /* Tests whether we the the connection has been terminated. We are careful to not stop processing it
+         * prematurely, since we want to handle half-open connections as well as possible and want to flush
+         * out and read data before we close down if we can. */
+
+        /* Already disconnected? */
+        if (!VARLINK_STATE_IS_ALIVE(v->state))
+                return 0;
+
+        /* Wait until connection setup is complete, i.e. until asynchronous connect() completes */
+        if (v->connecting)
+                return 0;
+
+        /* Still something to write and we can write? Stay around */
+        if (v->output_buffer_size > 0 && !v->write_disconnected)
+                return 0;
+
+        /* Both sides gone already? Then there's no need to stick around */
+        if (v->read_disconnected && v->write_disconnected)
+                goto disconnect;
+
+        /* If we are waiting for incoming data but the read side is shut down, disconnect. */
+        if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_CALLING, VARLINK_IDLE_SERVER) && v->read_disconnected)
+                goto disconnect;
+
+        /* Similar, if are a client that hasn't written anything yet but the write side is dead, also
+         * disconnect. We also explicitly check for POLLHUP here since we likely won't notice the write side
+         * being down if we never wrote anything. */
+        if (IN_SET(v->state, VARLINK_IDLE_CLIENT) && (v->write_disconnected || v->got_pollhup))
+                goto disconnect;
+
+        return 0;
+
+disconnect:
+        varlink_set_state(v, VARLINK_PENDING_DISCONNECT);
+        return 1;
+}
+
+static int varlink_write(Varlink *v) {
+        ssize_t n;
+
+        assert(v);
+
+        if (!VARLINK_STATE_IS_ALIVE(v->state))
+                return 0;
+        if (v->connecting) /* Writing while we are still wait for a non-blocking connect() to complete will
+                            * result in ENOTCONN, hence exit early here */
+                return 0;
+        if (v->output_buffer_size == 0)
+                return 0;
+        if (v->write_disconnected)
+                return 0;
+
+        assert(v->fd >= 0);
+
+        /* We generally prefer recv()/send() (mostly because of MSG_NOSIGNAL) but also want to be compatible
+         * with non-socket IO, hence fall back automatically */
+        if (!v->prefer_read_write) {
+                n = send(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL);
+                if (n < 0 && errno == ENOTSOCK)
+                        v->prefer_read_write = true;
+        }
+        if (v->prefer_read_write)
+                n = write(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size);
+        if (n < 0) {
+                if (errno == EAGAIN)
+                        return 0;
+
+                if (ERRNO_IS_DISCONNECT(errno)) {
+                        /* If we get informed about a disconnect on write, then let's remember that, but not
+                         * act on it just yet. Let's wait for read() to report the issue first. */
+                        v->write_disconnected = true;
+                        return 1;
+                }
+
+                return -errno;
+        }
+
+        v->output_buffer_size -= n;
+
+        if (v->output_buffer_size == 0)
+                v->output_buffer_index = 0;
+        else
+                v->output_buffer_index += n;
+
+        v->timestamp = now(CLOCK_MONOTONIC);
+        return 1;
+}
+
+static int varlink_read(Varlink *v) {
+        size_t rs;
+        ssize_t n;
+
+        assert(v);
+
+        if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_CALLING, VARLINK_IDLE_SERVER))
+                return 0;
+        if (v->connecting) /* read() on a socket while we are in connect() will fail with EINVAL, hence exit early here */
+                return 0;
+        if (v->current)
+                return 0;
+        if (v->input_buffer_unscanned > 0)
+                return 0;
+        if (v->read_disconnected)
+                return 0;
+
+        if (v->input_buffer_size >= VARLINK_BUFFER_MAX)
+                return -ENOBUFS;
+
+        assert(v->fd >= 0);
+
+        if (v->input_buffer_allocated <= v->input_buffer_index + v->input_buffer_size) {
+                size_t add;
+
+                add = MIN(VARLINK_BUFFER_MAX - v->input_buffer_size, VARLINK_READ_SIZE);
+
+                if (v->input_buffer_index == 0) {
+
+                        if (!GREEDY_REALLOC(v->input_buffer, v->input_buffer_allocated, v->input_buffer_size + add))
+                                return -ENOMEM;
+
+                } else {
+                        char *b;
+
+                        b = new(char, v->input_buffer_size + add);
+                        if (!b)
+                                return -ENOMEM;
+
+                        memcpy(b, v->input_buffer + v->input_buffer_index, v->input_buffer_size);
+
+                        free_and_replace(v->input_buffer, b);
+
+                        v->input_buffer_allocated = v->input_buffer_size + add;
+                        v->input_buffer_index = 0;
+                }
+        }
+
+        rs = v->input_buffer_allocated - (v->input_buffer_index + v->input_buffer_size);
+
+        if (!v->prefer_read_write) {
+                n = recv(v->fd, v->input_buffer + v->input_buffer_index + v->input_buffer_size, rs, MSG_DONTWAIT);
+                if (n < 0 && errno == ENOTSOCK)
+                        v->prefer_read_write = true;
+        }
+        if (v->prefer_read_write)
+                n = read(v->fd, v->input_buffer + v->input_buffer_index + v->input_buffer_size, rs);
+        if (n < 0) {
+                if (errno == EAGAIN)
+                        return 0;
+
+                if (ERRNO_IS_DISCONNECT(errno)) {
+                        v->read_disconnected = true;
+                        return 1;
+                }
+
+                return -errno;
+        }
+        if (n == 0) { /* EOF */
+                v->read_disconnected = true;
+                return 1;
+        }
+
+        v->input_buffer_size += n;
+        v->input_buffer_unscanned += n;
+
+        return 1;
+}
+
+static int varlink_parse_message(Varlink *v) {
+        const char *e, *begin;
+        size_t sz;
+        int r;
+
+        assert(v);
+
+        if (v->current)
+                return 0;
+        if (v->input_buffer_unscanned <= 0)
+                return 0;
+
+        assert(v->input_buffer_unscanned <= v->input_buffer_size);
+        assert(v->input_buffer_index + v->input_buffer_size <= v->input_buffer_allocated);
+
+        begin = v->input_buffer + v->input_buffer_index;
+
+        e = memchr(begin + v->input_buffer_size - v->input_buffer_unscanned, 0, v->input_buffer_unscanned);
+        if (!e) {
+                v->input_buffer_unscanned = 0;
+                return 0;
+        }
+
+        sz = e - begin + 1;
+
+        varlink_log(v, "New incoming message: %s", begin);
+
+        r = json_parse(begin, &v->current, NULL, NULL);
+        if (r < 0)
+                return r;
+
+        v->input_buffer_size -= sz;
+
+        if (v->input_buffer_size == 0)
+                v->input_buffer_index = 0;
+        else
+                v->input_buffer_index += sz;
+
+        v->input_buffer_unscanned = v->input_buffer_size;
+        return 1;
+}
+
+static int varlink_test_timeout(Varlink *v) {
+        assert(v);
+
+        if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_CALLING))
+                return 0;
+        if (v->timeout == USEC_INFINITY)
+                return 0;
+
+        if (now(CLOCK_MONOTONIC) < usec_add(v->timestamp, v->timeout))
+                return 0;
+
+        varlink_set_state(v, VARLINK_PENDING_TIMEOUT);
+
+        return 1;
+}
+
+static int varlink_dispatch_local_error(Varlink *v, const char *error) {
+        int r;
+
+        assert(v);
+        assert(error);
+
+        if (!v->reply_callback)
+                return 0;
+
+        r = v->reply_callback(v, NULL, error, VARLINK_REPLY_ERROR|VARLINK_REPLY_LOCAL, v->userdata);
+        if (r < 0)
+                log_debug_errno(r, "Reply callback returned error, ignoring: %m");
+
+        return 1;
+}
+
+static int varlink_dispatch_timeout(Varlink *v) {
+        assert(v);
+
+        if (v->state != VARLINK_PENDING_TIMEOUT)
+                return 0;
+
+        varlink_set_state(v, VARLINK_PROCESSING_TIMEOUT);
+        varlink_dispatch_local_error(v, VARLINK_ERROR_TIMEOUT);
+        varlink_close(v);
+
+        return 1;
+}
+
+static int varlink_dispatch_disconnect(Varlink *v) {
+        assert(v);
+
+        if (v->state != VARLINK_PENDING_DISCONNECT)
+                return 0;
+
+        varlink_set_state(v, VARLINK_PROCESSING_DISCONNECT);
+        varlink_dispatch_local_error(v, VARLINK_ERROR_DISCONNECTED);
+        varlink_close(v);
+
+        return 1;
+}
+
+static int varlink_sanitize_parameters(JsonVariant **v) {
+        assert(v);
+
+        /* Varlink always wants a parameters list, hence make one if the caller doesn't want any */
+        if (!*v)
+                return json_variant_new_object(v, NULL, 0);
+        else if (!json_variant_is_object(*v))
+                return -EINVAL;
+
+        return 0;
+}
+
+static int varlink_dispatch_reply(Varlink *v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        VarlinkReplyFlags flags = 0;
+        const char *error = NULL;
+        JsonVariant *e;
+        const char *k;
+        int r;
+
+        assert(v);
+
+        if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_CALLING))
+                return 0;
+        if (!v->current)
+                return 0;
+
+        assert(v->n_pending > 0);
+
+        if (!json_variant_is_object(v->current))
+                goto invalid;
+
+        JSON_VARIANT_OBJECT_FOREACH(k, e, v->current) {
+
+                if (streq(k, "error")) {
+                        if (error)
+                                goto invalid;
+                        if (!json_variant_is_string(e))
+                                goto invalid;
+
+                        error = json_variant_string(e);
+                        flags |= VARLINK_REPLY_ERROR;
+
+                } else if (streq(k, "parameters")) {
+                        if (parameters)
+                                goto invalid;
+                        if (!json_variant_is_object(e))
+                                goto invalid;
+
+                        parameters = json_variant_ref(e);
+
+                } else if (streq(k, "continues")) {
+                        if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
+                                goto invalid;
+
+                        if (!json_variant_is_boolean(e))
+                                goto invalid;
+
+                        if (json_variant_boolean(e))
+                                flags |= VARLINK_REPLY_CONTINUES;
+                } else
+                        goto invalid;
+        }
+
+        if (error && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
+                goto invalid;
+
+        r = varlink_sanitize_parameters(&parameters);
+        if (r < 0)
+                goto invalid;
+
+        if (v->state == VARLINK_AWAITING_REPLY) {
+                varlink_set_state(v, VARLINK_PROCESSING_REPLY);
+
+                if (v->reply_callback) {
+                        r = v->reply_callback(v, parameters, error, flags, v->userdata);
+                        if (r < 0)
+                                log_debug_errno(r, "Reply callback returned error, ignoring: %m");
+                }
+
+                v->current = json_variant_unref(v->current);
+
+                if (v->state == VARLINK_PROCESSING_REPLY) {
+                        assert(v->n_pending > 0);
+                        v->n_pending--;
+
+                        varlink_set_state(v, v->n_pending == 0 ? VARLINK_IDLE_CLIENT : VARLINK_AWAITING_REPLY);
+                }
+        } else {
+                assert(v->state == VARLINK_CALLING);
+
+                if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
+                        goto invalid;
+
+                varlink_set_state(v, VARLINK_CALLED);
+        }
+
+        return 1;
+
+invalid:
+        varlink_set_state(v, VARLINK_PROCESSING_FAILURE);
+        varlink_dispatch_local_error(v, VARLINK_ERROR_PROTOCOL);
+        varlink_close(v);
+
+        return 1;
+}
+
+static int varlink_dispatch_method(Varlink *v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        VarlinkMethodFlags flags = 0;
+        const char *method = NULL, *error;
+        JsonVariant *e;
+        VarlinkMethod callback;
+        const char *k;
+        int r;
+
+        assert(v);
+
+        if (v->state != VARLINK_IDLE_SERVER)
+                return 0;
+        if (!v->current)
+                return 0;
+
+        if (!json_variant_is_object(v->current))
+                goto invalid;
+
+        JSON_VARIANT_OBJECT_FOREACH(k, e, v->current) {
+
+                if (streq(k, "method")) {
+                        if (method)
+                                goto invalid;
+                        if (!json_variant_is_string(e))
+                                goto invalid;
+
+                        method = json_variant_string(e);
+
+                } else if (streq(k, "parameters")) {
+                        if (parameters)
+                                goto invalid;
+                        if (!json_variant_is_object(e))
+                                goto invalid;
+
+                        parameters = json_variant_ref(e);
+
+                } else if (streq(k, "oneway")) {
+
+                        if ((flags & (VARLINK_METHOD_ONEWAY|VARLINK_METHOD_MORE)) != 0)
+                                goto invalid;
+
+                        if (!json_variant_is_boolean(e))
+                                goto invalid;
+
+                        if (json_variant_boolean(e))
+                                flags |= VARLINK_METHOD_ONEWAY;
+
+                } else if (streq(k, "more")) {
+
+                        if ((flags & (VARLINK_METHOD_ONEWAY|VARLINK_METHOD_MORE)) != 0)
+                                goto invalid;
+
+                        if (!json_variant_is_boolean(e))
+                                goto invalid;
+
+                        if (json_variant_boolean(e))
+                                flags |= VARLINK_METHOD_MORE;
+
+                } else
+                        goto invalid;
+        }
+
+        if (!method)
+                goto invalid;
+
+        r = varlink_sanitize_parameters(&parameters);
+        if (r < 0)
+                goto fail;
+
+        varlink_set_state(v, (flags & VARLINK_METHOD_MORE)   ? VARLINK_PROCESSING_METHOD_MORE :
+                             (flags & VARLINK_METHOD_ONEWAY) ? VARLINK_PROCESSING_METHOD_ONEWAY :
+                                                               VARLINK_PROCESSING_METHOD);
+
+        assert(v->server);
+
+        if (STR_IN_SET(method, "org.varlink.service.GetInfo", "org.varlink.service.GetInterface")) {
+                /* For now, we don't implement a single of varlink's own methods */
+                callback = NULL;
+                error = VARLINK_ERROR_METHOD_NOT_IMPLEMENTED;
+        } else if (startswith(method, "org.varlink.service.")) {
+                callback = NULL;
+                error = VARLINK_ERROR_METHOD_NOT_FOUND;
+        } else {
+                callback = hashmap_get(v->server->methods, method);
+                error = VARLINK_ERROR_METHOD_NOT_FOUND;
+        }
+
+        if (callback) {
+                r = callback(v, parameters, flags, v->userdata);
+                if (r < 0) {
+                        log_debug_errno(r, "Callback for %s returned error: %m", method);
+
+                        /* We got an error back from the callback. Propagate it to the client if the method call remains unanswered. */
+                        if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) {
+                                r = varlink_errorb(v, VARLINK_ERROR_SYSTEM, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("errno", JSON_BUILD_INTEGER(-r))));
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+        } else if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) {
+                assert(error);
+
+                r = varlink_errorb(v, error, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method))));
+                if (r < 0)
+                        return r;
+        }
+
+        switch (v->state) {
+
+        case VARLINK_PROCESSED_METHOD: /* Method call is fully processed */
+        case VARLINK_PROCESSING_METHOD_ONEWAY: /* ditto */
+                v->current = json_variant_unref(v->current);
+                varlink_set_state(v, VARLINK_IDLE_SERVER);
+                break;
+
+        case VARLINK_PROCESSING_METHOD: /* Method call wasn't replied to, will be replied to later */
+                varlink_set_state(v, VARLINK_PENDING_METHOD);
+                break;
+
+        case VARLINK_PROCESSED_METHOD_MORE:  /* One reply for a "more" message was sent, more to come */
+        case VARLINK_PROCESSING_METHOD_MORE: /* No reply for a "more" message was sent, more to come */
+                varlink_set_state(v, VARLINK_PENDING_METHOD_MORE);
+                break;
+
+        default:
+                assert_not_reached("Unexpected state");
+
+        }
+
+        return r;
+
+invalid:
+        r = -EINVAL;
+
+fail:
+        varlink_set_state(v, VARLINK_PROCESSING_FAILURE);
+        varlink_dispatch_local_error(v, VARLINK_ERROR_PROTOCOL);
+        varlink_close(v);
+
+        return r;
+}
+
+int varlink_process(Varlink *v) {
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+
+        varlink_ref(v);
+
+        r = varlink_write(v);
+        if (r != 0)
+                goto finish;
+
+        r = varlink_dispatch_reply(v);
+        if (r != 0)
+                goto finish;
+
+        r = varlink_dispatch_method(v);
+        if (r != 0)
+                goto finish;
+
+        r = varlink_parse_message(v);
+        if (r != 0)
+                goto finish;
+
+        r = varlink_read(v);
+        if (r != 0)
+                goto finish;
+
+        r = varlink_test_disconnect(v);
+        if (r != 0)
+                goto finish;
+
+        r = varlink_dispatch_disconnect(v);
+        if (r != 0)
+                goto finish;
+
+        r = varlink_test_timeout(v);
+        if (r != 0)
+                goto finish;
+
+        r = varlink_dispatch_timeout(v);
+        if (r != 0)
+                goto finish;
+
+finish:
+        if (r >= 0 && v->defer_event_source) {
+                int q;
+
+                /* If we did some processing, make sure we are called again soon */
+                q = sd_event_source_set_enabled(v->defer_event_source, r > 0 ? SD_EVENT_ON : SD_EVENT_OFF);
+                if (q < 0)
+                        r = q;
+        }
+
+        if (r < 0) {
+                if (VARLINK_STATE_IS_ALIVE(v->state))
+                        /* Initiate disconnection */
+                        varlink_set_state(v, VARLINK_PENDING_DISCONNECT);
+                else
+                        /* We failed while disconnecting, in that case close right away */
+                        varlink_close(v);
+        }
+
+        varlink_unref(v);
+        return r;
+}
+
+static void handle_revents(Varlink *v, int revents) {
+        assert(v);
+
+        if (v->connecting) {
+                /* If we have seen POLLOUT or POLLHUP on a socket we are asynchronously waiting a connect()
+                 * to complete on, we know we are ready. We don't read the connection error here though,
+                 * we'll get the error on the next read() or write(). */
+                if ((revents & (POLLOUT|POLLHUP)) == 0)
+                        return;
+
+                varlink_log(v, "Anynchronous connection completed.");
+                v->connecting = false;
+        } else {
+                /* Note that we don't care much about POLLIN/POLLOUT here, we'll just try reading and writing
+                 * what we can. However, we do care about POLLHUP to detect connection termination even if we
+                 * momentarily don't want to read nor write anything. */
+
+                if (!FLAGS_SET(revents, POLLHUP))
+                        return;
+
+                varlink_log(v, "Got POLLHUP from socket.");
+                v->got_pollhup = true;
+        }
+}
+
+int varlink_wait(Varlink *v, usec_t timeout) {
+        struct timespec ts;
+        struct pollfd pfd;
+        int r, fd, events;
+        usec_t t;
+
+        assert_return(v, -EINVAL);
+        assert_return(!v->server, -ENOTTY);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+
+        r = varlink_get_timeout(v, &t);
+        if (r < 0)
+                return r;
+        if (t != USEC_INFINITY) {
+                usec_t n;
+
+                n = now(CLOCK_MONOTONIC);
+                if (t < n)
+                        t = 0;
+                else
+                        t = usec_sub_unsigned(t, n);
+        }
+
+        if (timeout != USEC_INFINITY &&
+            (t == USEC_INFINITY || timeout < t))
+                t = timeout;
+
+        fd = varlink_get_fd(v);
+        if (fd < 0)
+                return fd;
+
+        events = varlink_get_events(v);
+        if (events < 0)
+                return events;
+
+        pfd = (struct pollfd) {
+                .fd = fd,
+                .events = events,
+        };
+
+        r = ppoll(&pfd, 1,
+                  t == USEC_INFINITY ? NULL : timespec_store(&ts, t),
+                  NULL);
+        if (r < 0)
+                return -errno;
+
+        handle_revents(v, pfd.revents);
+
+        return r > 0 ? 1 : 0;
+}
+
+int varlink_get_fd(Varlink *v) {
+
+        assert_return(v, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+        if (v->fd < 0)
+                return -EBADF;
+
+        return v->fd;
+}
+
+int varlink_get_events(Varlink *v) {
+        int ret = 0;
+
+        assert_return(v, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+
+        if (v->connecting) /* When processing an asynchronous connect(), we only wait for EPOLLOUT, which
+                            * tells us that the connection is now complete. Before that we should neither
+                            * write() or read() from the fd. */
+                return EPOLLOUT;
+
+        if (!v->read_disconnected &&
+            IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_CALLING, VARLINK_IDLE_SERVER) &&
+            !v->current &&
+            v->input_buffer_unscanned <= 0)
+                ret |= EPOLLIN;
+
+        if (!v->write_disconnected &&
+            v->output_buffer_size > 0)
+                ret |= EPOLLOUT;
+
+        return ret;
+}
+
+int varlink_get_timeout(Varlink *v, usec_t *ret) {
+        assert_return(v, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+
+        if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_CALLING) &&
+            v->timeout != USEC_INFINITY) {
+                if (ret)
+                        *ret = usec_add(v->timestamp, v->timeout);
+                return 1;
+        } else {
+                if (ret)
+                        *ret = USEC_INFINITY;
+                return 0;
+        }
+}
+
+int varlink_flush(Varlink *v) {
+        int ret = 0, r;
+
+        assert_return(v, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+
+        for (;;) {
+                struct pollfd pfd;
+
+                if (v->output_buffer_size == 0)
+                        break;
+                if (v->write_disconnected)
+                        return -ECONNRESET;
+
+                r = varlink_write(v);
+                if (r < 0)
+                        return r;
+                if (r > 0) {
+                        ret = 1;
+                        continue;
+                }
+
+                pfd = (struct pollfd) {
+                        .fd = v->fd,
+                        .events = POLLOUT,
+                };
+
+                if (poll(&pfd, 1, -1) < 0)
+                        return -errno;
+
+                handle_revents(v, pfd.revents);
+        }
+
+        return ret;
+}
+
+static void varlink_detach_server(Varlink *v) {
+        assert(v);
+
+        if (!v->server)
+                return;
+
+        if (v->server->by_uid &&
+            v->ucred_acquired &&
+            uid_is_valid(v->ucred.uid)) {
+                unsigned c;
+
+                c = PTR_TO_UINT(hashmap_get(v->server->by_uid, UID_TO_PTR(v->ucred.uid)));
+                assert(c > 0);
+
+                if (c == 1)
+                        (void) hashmap_remove(v->server->by_uid, UID_TO_PTR(v->ucred.uid));
+                else
+                        (void) hashmap_replace(v->server->by_uid, UID_TO_PTR(v->ucred.uid), UINT_TO_PTR(c - 1));
+        }
+
+        assert(v->server->n_connections > 0);
+        v->server->n_connections--;
+
+        /* If this is a connection associated to a server, then let's disconnect the server and the
+         * connection from each other. This drops the dangling reference that connect_callback() set up. */
+        v->server = varlink_server_unref(v->server);
+        varlink_unref(v);
+}
+
+int varlink_close(Varlink *v) {
+
+        assert_return(v, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return 0;
+
+        varlink_set_state(v, VARLINK_DISCONNECTED);
+
+        /* Let's take a reference first, since varlink_detach_server() might drop the final (dangling) ref
+         * which would destroy us before we can call varlink_clear() */
+        varlink_ref(v);
+        varlink_detach_server(v);
+        varlink_clear(v);
+        varlink_unref(v);
+
+        return 1;
+}
+
+Varlink* varlink_flush_close_unref(Varlink *v) {
+
+        if (!v)
+                return NULL;
+
+        (void) varlink_flush(v);
+        (void) varlink_close(v);
+
+        return varlink_unref(v);
+}
+
+static int varlink_enqueue_json(Varlink *v, JsonVariant *m) {
+        _cleanup_free_ char *text = NULL;
+        int r;
+
+        assert(v);
+        assert(m);
+
+        r = json_variant_format(m, 0, &text);
+        if (r < 0)
+                return r;
+
+        if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX)
+                return -ENOBUFS;
+
+        varlink_log(v, "Sending message: %s", text);
+
+        if (v->output_buffer_size == 0) {
+
+                free_and_replace(v->output_buffer, text);
+
+                v->output_buffer_size = v->output_buffer_allocated = r + 1;
+                v->output_buffer_index = 0;
+
+        } else if (v->output_buffer_index == 0) {
+
+                if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_allocated, v->output_buffer_size + r + 1))
+                        return -ENOMEM;
+
+                memcpy(v->output_buffer + v->output_buffer_size, text, r + 1);
+                v->output_buffer_size += r + 1;
+
+        } else {
+                char *n;
+
+                n = new(char, v->output_buffer_size + r + 1);
+                if (!n)
+                        return -ENOMEM;
+
+                memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, r + 1);
+
+                free_and_replace(v->output_buffer, n);
+                v->output_buffer_size += r + 1;
+                v->output_buffer_index = 0;
+        }
+
+        return 0;
+}
+
+int varlink_send(Varlink *v, const char *method, JsonVariant *parameters) {
+        _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(method, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+        if (!IN_SET(v->state, VARLINK_IDLE_CLIENT, VARLINK_AWAITING_REPLY))
+                return -EBUSY;
+
+        r = varlink_sanitize_parameters(&parameters);
+        if (r < 0)
+                return r;
+
+        r = json_build(&m, JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)),
+                                       JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters)),
+                                       JSON_BUILD_PAIR("oneway", JSON_BUILD_BOOLEAN(true))));
+        if (r < 0)
+                return r;
+
+        r = varlink_enqueue_json(v, m);
+        if (r < 0)
+                return r;
+
+        /* No state change here, this is one-way only after all */
+        v->timestamp = now(CLOCK_MONOTONIC);
+        return 0;
+}
+
+int varlink_sendb(Varlink *v, const char *method, ...) {
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        va_list ap;
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        va_start(ap, method);
+        r = json_buildv(&parameters, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return r;
+
+        return varlink_send(v, method, parameters);
+}
+
+int varlink_invoke(Varlink *v, const char *method, JsonVariant *parameters) {
+        _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(method, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+        if (!IN_SET(v->state, VARLINK_IDLE_CLIENT, VARLINK_AWAITING_REPLY))
+                return -EBUSY;
+
+        r = varlink_sanitize_parameters(&parameters);
+        if (r < 0)
+                return r;
+
+        r = json_build(&m, JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)),
+                                       JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters))));
+        if (r < 0)
+                return r;
+
+        r = varlink_enqueue_json(v, m);
+        if (r < 0)
+                return r;
+
+        varlink_set_state(v, VARLINK_AWAITING_REPLY);
+        v->n_pending++;
+        v->timestamp = now(CLOCK_MONOTONIC);
+
+        return 0;
+}
+
+int varlink_invokeb(Varlink *v, const char *method, ...) {
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        va_list ap;
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        va_start(ap, method);
+        r = json_buildv(&parameters, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return r;
+
+        return varlink_invoke(v, method, parameters);
+}
+
+int varlink_call(
+                Varlink *v,
+                const char *method,
+                JsonVariant *parameters,
+                JsonVariant **ret_parameters,
+                const char **ret_error_id,
+                VarlinkReplyFlags *ret_flags) {
+
+        _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(method, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+        if (!IN_SET(v->state, VARLINK_IDLE_CLIENT))
+                return -EBUSY;
+
+        assert(v->n_pending == 0); /* n_pending can't be > 0 if we are in VARLINK_IDLE_CLIENT state */
+
+        r = varlink_sanitize_parameters(&parameters);
+        if (r < 0)
+                return r;
+
+        r = json_build(&m, JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)),
+                                       JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters))));
+        if (r < 0)
+                return r;
+
+        r = varlink_enqueue_json(v, m);
+        if (r < 0)
+                return r;
+
+        varlink_set_state(v, VARLINK_CALLING);
+        v->n_pending++;
+        v->timestamp = now(CLOCK_MONOTONIC);
+
+        while (v->state == VARLINK_CALLING) {
+
+                r = varlink_process(v);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        continue;
+
+                r = varlink_wait(v, USEC_INFINITY);
+                if (r < 0)
+                        return r;
+        }
+
+        switch (v->state) {
+
+        case VARLINK_CALLED:
+                assert(v->current);
+
+                json_variant_unref(v->reply);
+                v->reply = TAKE_PTR(v->current);
+
+                varlink_set_state(v, VARLINK_IDLE_CLIENT);
+                assert(v->n_pending == 1);
+                v->n_pending--;
+
+                if (ret_parameters)
+                        *ret_parameters = json_variant_by_key(v->reply, "parameters");
+                if (ret_error_id)
+                        *ret_error_id = json_variant_string(json_variant_by_key(v->reply, "error"));
+                if (ret_flags)
+                        *ret_flags = 0;
+
+                return 1;
+
+        case VARLINK_PENDING_DISCONNECT:
+        case VARLINK_DISCONNECTED:
+                return -ECONNRESET;
+
+        case VARLINK_PENDING_TIMEOUT:
+                return -ETIME;
+
+        default:
+                assert_not_reached("Unexpected state after method call.");
+        }
+}
+
+int varlink_callb(
+                Varlink *v,
+                const char *method,
+                JsonVariant **ret_parameters,
+                const char **ret_error_id,
+                VarlinkReplyFlags *ret_flags, ...) {
+
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        va_list ap;
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        va_start(ap, ret_flags);
+        r = json_buildv(&parameters, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return r;
+
+        return varlink_call(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
+}
+
+int varlink_reply(Varlink *v, JsonVariant *parameters) {
+        _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+        if (!IN_SET(v->state,
+                    VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE,
+                    VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE))
+                return -EBUSY;
+
+        r = varlink_sanitize_parameters(&parameters);
+        if (r < 0)
+                return r;
+
+        r = json_build(&m, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters))));
+        if (r < 0)
+                return r;
+
+        r = varlink_enqueue_json(v, m);
+        if (r < 0)
+                return r;
+
+        if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) {
+                /* We just replied to a method call that was let hanging for a while (i.e. we were outside of
+                 * the varlink_dispatch_method() stack frame), which means with this reply we are ready to
+                 * process further messages. */
+                v->current = json_variant_unref(v->current);
+                varlink_set_state(v, VARLINK_IDLE_SERVER);
+        } else
+                /* We replied to a method call from within the varlink_dispatch_method() stack frame), which
+                 * means we should it handle the rest of the state engine. */
+                varlink_set_state(v, VARLINK_PROCESSED_METHOD);
+
+        return 1;
+}
+
+int varlink_replyb(Varlink *v, ...) {
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        va_list ap;
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        va_start(ap, v);
+        r = json_buildv(&parameters, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return r;
+
+        return varlink_reply(v, parameters);
+}
+
+int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters) {
+        _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(error_id, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+        if (!IN_SET(v->state,
+                    VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE,
+                    VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE))
+                return -EBUSY;
+
+        r = varlink_sanitize_parameters(&parameters);
+        if (r < 0)
+                return r;
+
+        r = json_build(&m, JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("error", JSON_BUILD_STRING(error_id)),
+                                       JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters))));
+        if (r < 0)
+                return r;
+
+        r = varlink_enqueue_json(v, m);
+        if (r < 0)
+                return r;
+
+        if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) {
+                v->current = json_variant_unref(v->current);
+                varlink_set_state(v, VARLINK_IDLE_SERVER);
+        } else
+                varlink_set_state(v, VARLINK_PROCESSED_METHOD);
+
+        return 1;
+}
+
+int varlink_errorb(Varlink *v, const char *error_id, ...) {
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        va_list ap;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(error_id, -EINVAL);
+
+        va_start(ap, error_id);
+        r = json_buildv(&parameters, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return r;
+
+        return varlink_error(v, error_id, parameters);
+}
+
+int varlink_error_invalid_parameter(Varlink *v, JsonVariant *parameters) {
+
+        assert_return(v, -EINVAL);
+        assert_return(parameters, -EINVAL);
+
+        /* We expect to be called in one of two ways: the 'parameters' argument is a string variant in which
+         * case it is the parameter key name that is invalid. Or the 'parameters' argument is an object
+         * variant in which case we'll pull out the first key. The latter mode is useful in functions that
+         * don't expect any arguments. */
+
+        if (json_variant_is_string(parameters))
+                return varlink_error(v, VARLINK_ERROR_INVALID_PARAMETER, parameters);
+
+        if (json_variant_is_object(parameters) &&
+            json_variant_elements(parameters) > 0)
+                return varlink_error(v, VARLINK_ERROR_INVALID_PARAMETER,
+                                     json_variant_by_index(parameters, 0));
+
+        return -EINVAL;
+}
+
+int varlink_notify(Varlink *v, JsonVariant *parameters) {
+        _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        if (v->state == VARLINK_DISCONNECTED)
+                return -ENOTCONN;
+        if (!IN_SET(v->state, VARLINK_PROCESSING_METHOD_MORE, VARLINK_PENDING_METHOD_MORE))
+                return -EBUSY;
+
+        r = varlink_sanitize_parameters(&parameters);
+        if (r < 0)
+                return r;
+
+        r = json_build(&m, JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters)),
+                                       JSON_BUILD_PAIR("continues", JSON_BUILD_BOOLEAN(true))));
+        if (r < 0)
+                return r;
+
+        r = varlink_enqueue_json(v, m);
+        if (r < 0)
+                return r;
+
+        /* No state change, as more is coming */
+        return 1;
+}
+
+int varlink_notifyb(Varlink *v, ...) {
+        _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+        va_list ap;
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        va_start(ap, v);
+        r = json_buildv(&parameters, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return r;
+
+        return varlink_notify(v, parameters);
+}
+
+int varlink_bind_reply(Varlink *v, VarlinkReply callback) {
+        assert_return(v, -EINVAL);
+
+        if (callback && v->reply_callback && callback != v->reply_callback)
+                return -EBUSY;
+
+        v->reply_callback = callback;
+
+        return 0;
+}
+
+void* varlink_set_userdata(Varlink *v, void *userdata) {
+        void *old;
+
+        assert_return(v, NULL);
+
+        old = v->userdata;
+        v->userdata = userdata;
+
+        return old;
+}
+
+void* varlink_get_userdata(Varlink *v) {
+        assert_return(v, NULL);
+
+        return v->userdata;
+}
+
+static int varlink_acquire_ucred(Varlink *v) {
+        int r;
+
+        assert(v);
+
+        if (v->ucred_acquired)
+                return 0;
+
+        r = getpeercred(v->fd, &v->ucred);
+        if (r < 0)
+                return r;
+
+        v->ucred_acquired = true;
+        return 0;
+}
+
+int varlink_get_peer_uid(Varlink *v, uid_t *ret) {
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        r = varlink_acquire_ucred(v);
+        if (r < 0)
+                return r;
+
+        if (!uid_is_valid(v->ucred.uid))
+                return -ENODATA;
+
+        *ret = v->ucred.uid;
+        return 0;
+}
+
+int varlink_get_peer_pid(Varlink *v, pid_t *ret) {
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        r = varlink_acquire_ucred(v);
+        if (r < 0)
+                return r;
+
+        if (!pid_is_valid(v->ucred.pid))
+                return -ENODATA;
+
+        *ret = v->ucred.pid;
+        return 0;
+}
+
+int varlink_set_relative_timeout(Varlink *v, usec_t timeout) {
+        assert_return(v, -EINVAL);
+        assert_return(timeout > 0, -EINVAL);
+
+        v->timeout = timeout;
+        return 0;
+}
+
+VarlinkServer *varlink_get_server(Varlink *v) {
+        assert_return(v, NULL);
+
+        return v->server;
+}
+
+int varlink_set_description(Varlink *v, const char *description) {
+        assert_return(v, -EINVAL);
+
+        return free_and_strdup(&v->description, description);
+}
+
+static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        Varlink *v = userdata;
+
+        assert(s);
+        assert(v);
+
+        handle_revents(v, revents);
+        (void) varlink_process(v);
+
+        return 1;
+}
+
+static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) {
+        Varlink *v = userdata;
+
+        assert(s);
+        assert(v);
+
+        (void) varlink_process(v);
+        return 1;
+}
+
+static int defer_callback(sd_event_source *s, void *userdata) {
+        Varlink *v = userdata;
+
+        assert(s);
+        assert(v);
+
+        (void) varlink_process(v);
+        return 1;
+}
+
+static int prepare_callback(sd_event_source *s, void *userdata) {
+        Varlink *v = userdata;
+        int r, e;
+        usec_t until;
+
+        assert(s);
+        assert(v);
+
+        e = varlink_get_events(v);
+        if (e < 0)
+                return e;
+
+        r = sd_event_source_set_io_events(v->io_event_source, e);
+        if (r < 0)
+                return r;
+
+        r = varlink_get_timeout(v, &until);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                r = sd_event_source_set_time(v->time_event_source, until);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_event_source_set_enabled(v->time_event_source, r > 0 ? SD_EVENT_ON : SD_EVENT_OFF);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+static int quit_callback(sd_event_source *event, void *userdata) {
+        Varlink *v = userdata;
+
+        assert(event);
+        assert(v);
+
+        varlink_flush(v);
+        varlink_close(v);
+
+        return 1;
+}
+
+int varlink_attach_event(Varlink *v, sd_event *e, int64_t priority) {
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(!v->event, -EBUSY);
+
+        if (e)
+                v->event = sd_event_ref(e);
+        else {
+                r = sd_event_default(&v->event);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_event_add_time(v->event, &v->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, v);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_priority(v->time_event_source, priority);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(v->time_event_source, "varlink-time");
+
+        r = sd_event_add_exit(v->event, &v->quit_event_source, quit_callback, v);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_priority(v->quit_event_source, priority);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(v->quit_event_source, "varlink-quit");
+
+        r = sd_event_add_io(v->event, &v->io_event_source, v->fd, 0, io_callback, v);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_prepare(v->io_event_source, prepare_callback);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_priority(v->io_event_source, priority);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(v->io_event_source, "varlink-io");
+
+        r = sd_event_add_defer(v->event, &v->defer_event_source, defer_callback, v);
+        if (r < 0)
+                goto fail;
+
+        r = sd_event_source_set_priority(v->defer_event_source, priority);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(v->defer_event_source, "varlink-defer");
+
+        return 0;
+
+fail:
+        varlink_detach_event(v);
+        return r;
+}
+
+
+void varlink_detach_event(Varlink *v) {
+        if (!v)
+                return;
+
+        varlink_detach_event_sources(v);
+
+        v->event = sd_event_unref(v->event);
+}
+
+sd_event *varlink_get_event(Varlink *v) {
+        assert_return(v, NULL);
+
+        return v->event;
+}
+
+int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
+        VarlinkServer *s;
+
+        assert_return(ret, -EINVAL);
+        assert_return((flags & ~_VARLINK_SERVER_FLAGS_ALL) == 0, -EINVAL);
+
+        s = new(VarlinkServer, 1);
+        if (!s)
+                return -ENOMEM;
+
+        *s = (VarlinkServer) {
+                .n_ref = 1,
+                .flags = flags,
+                .connections_max = varlink_server_connections_max(NULL),
+                .connections_per_uid_max = varlink_server_connections_per_uid_max(NULL),
+        };
+
+        *ret = s;
+        return 0;
+}
+
+static VarlinkServer* varlink_server_destroy(VarlinkServer *s) {
+        char *m;
+
+        if (!s)
+                return NULL;
+
+        varlink_server_shutdown(s);
+
+        while ((m = hashmap_steal_first_key(s->methods)))
+                free(m);
+
+        hashmap_free(s->methods);
+        hashmap_free(s->by_uid);
+
+        sd_event_unref(s->event);
+
+        free(s->description);
+
+        return mfree(s);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(VarlinkServer, varlink_server, varlink_server_destroy);
+
+static int validate_connection(VarlinkServer *server, const struct ucred *ucred) {
+        int allowed = -1;
+
+        assert(server);
+        assert(ucred);
+
+        if (FLAGS_SET(server->flags, VARLINK_SERVER_ROOT_ONLY))
+                allowed = ucred->uid == 0;
+
+        if (FLAGS_SET(server->flags, VARLINK_SERVER_MYSELF_ONLY))
+                allowed = allowed > 0 || ucred->uid == getuid();
+
+        if (allowed == 0) { /* Allow access when it is explicitly allowed or when neither
+                             * VARLINK_SERVER_ROOT_ONLY nor VARLINK_SERVER_MYSELF_ONLY are specified. */
+                varlink_server_log(server, "Unprivileged client attempted connection, refusing.");
+                return 0;
+        }
+
+        if (server->n_connections >= server->connections_max) {
+                varlink_server_log(server, "Connection limit of %u reached, refusing.", server->connections_max);
+                return 0;
+        }
+
+        if (FLAGS_SET(server->flags, VARLINK_SERVER_ACCOUNT_UID)) {
+                unsigned c;
+
+                if (!uid_is_valid(ucred->uid)) {
+                        varlink_server_log(server, "Client with invalid UID attempted connection, refusing.");
+                        return 0;
+                }
+
+                c = PTR_TO_UINT(hashmap_get(server->by_uid, UID_TO_PTR(ucred->uid)));
+                if (c >= server->connections_per_uid_max) {
+                        varlink_server_log(server, "Per-UID connection limit of %u reached, refusing.",
+                                           server->connections_per_uid_max);
+                        return 0;
+                }
+        }
+
+        return 1;
+}
+
+static int count_connection(VarlinkServer *server, struct ucred *ucred) {
+        unsigned c;
+        int r;
+
+        assert(server);
+        assert(ucred);
+
+        server->n_connections++;
+
+        if (FLAGS_SET(server->flags, VARLINK_SERVER_ACCOUNT_UID)) {
+                r = hashmap_ensure_allocated(&server->by_uid, NULL);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to allocate UID hash table: %m");
+
+                c = PTR_TO_UINT(hashmap_get(server->by_uid, UID_TO_PTR(ucred->uid)));
+
+                varlink_server_log(server, "Connections of user " UID_FMT ": %u (of %u max)",
+                                   ucred->uid, c, server->connections_per_uid_max);
+
+                r = hashmap_replace(server->by_uid, UID_TO_PTR(ucred->uid), UINT_TO_PTR(c + 1));
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to increment counter in UID hash table: %m");
+        }
+
+        return 0;
+}
+
+int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) {
+        _cleanup_(varlink_unrefp) Varlink *v = NULL;
+        bool ucred_acquired;
+        struct ucred ucred;
+        int r;
+
+        assert_return(server, -EINVAL);
+        assert_return(fd >= 0, -EBADF);
+
+        if ((server->flags & (VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_ACCOUNT_UID)) != 0) {
+                r = getpeercred(fd, &ucred);
+                if (r < 0)
+                        return varlink_server_log_errno(server, r, "Failed to acquire peer credentials of incoming socket, refusing: %m");
+
+                ucred_acquired = true;
+
+                r = validate_connection(server, &ucred);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EPERM;
+        } else
+                ucred_acquired = false;
+
+        r = varlink_new(&v);
+        if (r < 0)
+                return varlink_server_log_errno(server, r, "Failed to allocate connection object: %m");
+
+        r = count_connection(server, &ucred);
+        if (r < 0)
+                return r;
+
+        v->fd = fd;
+        v->userdata = server->userdata;
+        if (ucred_acquired) {
+                v->ucred = ucred;
+                v->ucred_acquired = true;
+        }
+
+        (void) asprintf(&v->description, "%s-%i", server->description ?: "varlink", v->fd);
+
+        /* Link up the server and the connection, and take reference in both directions. Note that the
+         * reference on the connection is left dangling. It will be dropped when the connection is closed,
+         * which happens in varlink_close(), including in the event loop quit callback. */
+        v->server = varlink_server_ref(server);
+        varlink_ref(v);
+
+        varlink_set_state(v, VARLINK_IDLE_SERVER);
+
+        r = varlink_attach_event(v, server->event, server->event_priority);
+        if (r < 0) {
+                varlink_log_errno(v, r, "Failed to attach new connection: %m");
+                v->fd = -1; /* take the fd out of the connection again */
+                varlink_close(v);
+                return r;
+        }
+
+        if (ret)
+                *ret = v;
+
+        return 0;
+}
+
+static int connect_callback(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+        VarlinkServerSocket *ss = userdata;
+        _cleanup_close_ int cfd = -1;
+        Varlink *v = NULL;
+        int r;
+
+        assert(source);
+        assert(ss);
+
+        varlink_server_log(ss->server, "New incoming connection.");
+
+        cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+        if (cfd < 0) {
+                if (ERRNO_IS_ACCEPT_AGAIN(errno))
+                        return 0;
+
+                return varlink_server_log_errno(ss->server, errno, "Failed to accept incoming socket: %m");
+        }
+
+        r = varlink_server_add_connection(ss->server, cfd, &v);
+        if (r < 0)
+                return 0;
+
+        TAKE_FD(cfd);
+
+        if (ss->server->connect_callback) {
+                r = ss->server->connect_callback(ss->server, v, ss->server->userdata);
+                if (r < 0) {
+                        varlink_log_errno(v, r, "Connection callback returned error, disconnecting client: %m");
+                        varlink_close(v);
+                        return 0;
+                }
+        }
+
+        return 0;
+}
+
+int varlink_server_listen_fd(VarlinkServer *s, int fd) {
+        _cleanup_free_ VarlinkServerSocket *ss = NULL;
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(fd >= 0, -EBADF);
+
+        r = fd_nonblock(fd, true);
+        if (r < 0)
+                return r;
+
+        ss = new(VarlinkServerSocket, 1);
+        if (!ss)
+                return -ENOMEM;
+
+        *ss = (VarlinkServerSocket) {
+                .server = s,
+                .fd = fd,
+        };
+
+        if (s->event) {
+                _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL;
+
+                r = sd_event_add_io(s->event, &es, fd, EPOLLIN, connect_callback, ss);
+                if (r < 0)
+                        return r;
+
+                r = sd_event_source_set_priority(ss->event_source, s->event_priority);
+                if (r < 0)
+                        return r;
+        }
+
+        LIST_PREPEND(sockets, s->sockets, TAKE_PTR(ss));
+        return 0;
+}
+
+int varlink_server_listen_address(VarlinkServer *s, const char *address, mode_t m) {
+        union sockaddr_union sockaddr;
+        _cleanup_close_ int fd = -1;
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(address, -EINVAL);
+        assert_return((m & ~0777) == 0, -EINVAL);
+
+        r = sockaddr_un_set_path(&sockaddr.un, address);
+        if (r < 0)
+                return r;
+
+        fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+        if (fd < 0)
+                return -errno;
+
+        (void) sockaddr_un_unlink(&sockaddr.un);
+
+        RUN_WITH_UMASK(~m & 0777)
+                if (bind(fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0)
+                        return -errno;
+
+        if (listen(fd, SOMAXCONN) < 0)
+                return -errno;
+
+        r = varlink_server_listen_fd(s, fd);
+        if (r < 0)
+                return r;
+
+        TAKE_FD(fd);
+        return 0;
+}
+
+void* varlink_server_set_userdata(VarlinkServer *s, void *userdata) {
+        void *ret;
+
+        assert_return(s, NULL);
+
+        ret = s->userdata;
+        s->userdata = userdata;
+
+        return ret;
+}
+
+void* varlink_server_get_userdata(VarlinkServer *s) {
+        assert_return(s, NULL);
+
+        return s->userdata;
+}
+
+static VarlinkServerSocket* varlink_server_socket_destroy(VarlinkServerSocket *ss) {
+        if (!ss)
+                return NULL;
+
+        if (ss->server)
+                LIST_REMOVE(sockets, ss->server->sockets, ss);
+
+        sd_event_source_disable_unref(ss->event_source);
+
+        free(ss->address);
+        safe_close(ss->fd);
+
+        return mfree(ss);
+}
+
+int varlink_server_shutdown(VarlinkServer *s) {
+        assert_return(s, -EINVAL);
+
+        while (s->sockets)
+                varlink_server_socket_destroy(s->sockets);
+
+        return 0;
+}
+
+int varlink_server_attach_event(VarlinkServer *s, sd_event *e, int64_t priority) {
+        VarlinkServerSocket *ss;
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(!s->event, -EBUSY);
+
+        if (e)
+                s->event = sd_event_ref(e);
+        else {
+                r = sd_event_default(&s->event);
+                if (r < 0)
+                        return r;
+        }
+
+        LIST_FOREACH(sockets, ss, s->sockets) {
+                assert(!ss->event_source);
+
+                r = sd_event_add_io(s->event, &ss->event_source, ss->fd, EPOLLIN, connect_callback, ss);
+                if (r < 0)
+                        goto fail;
+
+                r = sd_event_source_set_priority(ss->event_source, priority);
+                if (r < 0)
+                        goto fail;
+        }
+
+        s->event_priority = priority;
+        return 0;
+
+fail:
+        varlink_server_detach_event(s);
+        return r;
+}
+
+int varlink_server_detach_event(VarlinkServer *s) {
+        VarlinkServerSocket *ss;
+
+        assert_return(s, -EINVAL);
+
+        LIST_FOREACH(sockets, ss, s->sockets) {
+
+                if (!ss->event_source)
+                        continue;
+
+                (void) sd_event_source_set_enabled(ss->event_source, SD_EVENT_OFF);
+                ss->event_source = sd_event_source_unref(ss->event_source);
+        }
+
+        sd_event_unref(s->event);
+        return 0;
+}
+
+sd_event *varlink_server_get_event(VarlinkServer *s) {
+        assert_return(s, NULL);
+
+        return s->event;
+}
+
+int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMethod callback) {
+        char *m;
+        int r;
+
+        assert_return(s, -EINVAL);
+        assert_return(method, -EINVAL);
+        assert_return(callback, -EINVAL);
+
+        if (startswith(method, "org.varlink.service."))
+                return -EEXIST;
+
+        r = hashmap_ensure_allocated(&s->methods, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        m = strdup(method);
+        if (!m)
+                return -ENOMEM;
+
+        r = hashmap_put(s->methods, m, callback);
+        if (r < 0) {
+                free(m);
+                return r;
+        }
+
+        return 0;
+}
+
+int varlink_server_bind_method_many_internal(VarlinkServer *s, ...) {
+        va_list ap;
+        int r;
+
+        assert_return(s, -EINVAL);
+
+        va_start(ap, s);
+        for (;;) {
+                VarlinkMethod callback;
+                const char *method;
+
+                method = va_arg(ap, const char *);
+                if (!method)
+                        break;
+
+                callback = va_arg(ap, VarlinkMethod);
+
+                r = varlink_server_bind_method(s, method, callback);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect callback) {
+        assert_return(s, -EINVAL);
+
+        if (callback && s->connect_callback && callback != s->connect_callback)
+                return -EBUSY;
+
+        s->connect_callback = callback;
+        return 0;
+}
+
+unsigned varlink_server_connections_max(VarlinkServer *s) {
+        struct rlimit rl;
+
+        /* If a server is specified, return the setting for that server, otherwise the default value */
+        if (s)
+                return s->connections_max;
+
+        assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0);
+
+        /* Make sure we never use up more than Â¾th of RLIMIT_NOFILE for IPC */
+        if (VARLINK_DEFAULT_CONNECTIONS_MAX > rl.rlim_cur / 4 * 3)
+                return rl.rlim_cur / 4 * 3;
+
+        return VARLINK_DEFAULT_CONNECTIONS_MAX;
+}
+
+unsigned varlink_server_connections_per_uid_max(VarlinkServer *s) {
+        unsigned m;
+
+        if (s)
+                return s->connections_per_uid_max;
+
+        /* Make sure to never use up more than Â¾th of available connections for a single user */
+        m = varlink_server_connections_max(NULL);
+        if (VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX > m)
+                return m / 4 * 3;
+
+        return VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX;
+}
+
+int varlink_server_set_connections_per_uid_max(VarlinkServer *s, unsigned m) {
+        assert_return(s, -EINVAL);
+        assert_return(m > 0, -EINVAL);
+
+        s->connections_per_uid_max = m;
+        return 0;
+}
+
+int varlink_server_set_connections_max(VarlinkServer *s, unsigned m) {
+        assert_return(s, -EINVAL);
+        assert_return(m > 0, -EINVAL);
+
+        s->connections_max = m;
+        return 0;
+}
+
+int varlink_server_set_description(VarlinkServer *s, const char *description) {
+        assert_return(s, -EINVAL);
+
+        return free_and_strdup(&s->description, description);
+}
diff --git a/src/shared/varlink.h b/src/shared/varlink.h
new file mode 100644 (file)
index 0000000..d96fa93
--- /dev/null
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-event.h"
+
+#include "json.h"
+#include "time-util.h"
+
+/* A minimal Varlink implementation. We only implement the minimal, obvious bits here though. No validation,
+ * no introspection, no name service, just the stuff actually needed.
+ *
+ * You might wonder why we aren't using libvarlink here? Varlink is a very simple protocol, which allows us
+ * to write our own implementation relatively easily. However, the main reasons are these:
+ *
+ * â€¢ We want to use our own JSON subsystem, with all the benefits that brings (i.e. accurate unsigned+signed
+ *   64bit integers, full fuzzing, logging during parsing and so on). If we'd want to use that with
+ *   libvarlink we'd have to serialize and deserialize all the time from its own representation which is
+ *   inefficient and nasty.
+ *
+ * â€¢ We want integration into sd-event, but also synchronous event-loop-less operation
+ *
+ * â€¢ We need proper per-UID accounting and access control, since we want to allow communication between
+ *   unprivileged clients and privileged servers.
+ *
+ * â€¢ And of course, we don't want the name service and introspection stuff for now (though that might
+ *   change).
+ */
+
+typedef struct Varlink Varlink;
+typedef struct VarlinkServer VarlinkServer;
+
+typedef enum VarlinkReplyFlags {
+        VARLINK_REPLY_ERROR     = 1 << 0,
+        VARLINK_REPLY_CONTINUES = 1 << 1,
+        VARLINK_REPLY_LOCAL     = 1 << 2,
+} VarlinkReplyFlags;
+
+typedef enum VarlinkMethodFlags {
+        VARLINK_METHOD_ONEWAY = 1 << 0,
+        VARLINK_METHOD_MORE   = 2 << 1,
+} VarlinkMethodFlags;
+
+typedef enum VarlinkServerFlags {
+        VARLINK_SERVER_ROOT_ONLY   = 1 << 0, /* Only accessible by root */
+        VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */
+        VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */
+
+        _VARLINK_SERVER_FLAGS_ALL = (1 << 3) - 1,
+} VarlinkServerFlags;
+
+typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
+typedef int (*VarlinkReply)(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata);
+typedef int (*VarlinkConnect)(VarlinkServer *server, Varlink *link, void *userdata);
+
+int varlink_connect_address(Varlink **ret, const char *address);
+int varlink_connect_fd(Varlink **ret, int fd);
+
+Varlink* varlink_ref(Varlink *link);
+Varlink* varlink_unref(Varlink *v);
+
+int varlink_get_fd(Varlink *v);
+int varlink_get_events(Varlink *v);
+int varlink_get_timeout(Varlink *v, usec_t *ret);
+
+int varlink_attach_event(Varlink *v, sd_event *e, int64_t priority);
+void varlink_detach_event(Varlink *v);
+sd_event *varlink_get_event(Varlink *v);
+
+int varlink_process(Varlink *v);
+int varlink_wait(Varlink *v, usec_t timeout);
+
+int varlink_flush(Varlink *v);
+int varlink_close(Varlink *v);
+
+Varlink* varlink_flush_close_unref(Varlink *v);
+
+/* Enqueue method call, not expecting a reply */
+int varlink_send(Varlink *v, const char *method, JsonVariant *parameters);
+int varlink_sendb(Varlink *v, const char *method, ...);
+
+/* Send method call and wait for reply */
+int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
+int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...);
+
+/* Enqueue method call, expect a reply, which is eventually delivered to the reply callback */
+int varlink_invoke(Varlink *v, const char *method, JsonVariant *parameters);
+int varlink_invokeb(Varlink *v, const char *method, ...);
+
+/* Enqueue a final reply */
+int varlink_reply(Varlink *v, JsonVariant *parameters);
+int varlink_replyb(Varlink *v, ...);
+
+/* Enqueue a (final) error */
+int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters);
+int varlink_errorb(Varlink *v, const char *error_id, ...);
+int varlink_error_invalid_parameter(Varlink *v, JsonVariant *parameters);
+
+/* Enqueue a "more" reply */
+int varlink_notify(Varlink *v, JsonVariant *parameters);
+int varlink_notifyb(Varlink *v, ...);
+
+/* Bind a disconnect, reply or timeout callback */
+int varlink_bind_reply(Varlink *v, VarlinkReply reply);
+
+void* varlink_set_userdata(Varlink *v, void *userdata);
+void* varlink_get_userdata(Varlink *v);
+
+int varlink_get_peer_uid(Varlink *v, uid_t *ret);
+int varlink_get_peer_pid(Varlink *v, pid_t *ret);
+
+int varlink_set_relative_timeout(Varlink *v, usec_t usec);
+
+VarlinkServer* varlink_get_server(Varlink *v);
+
+int varlink_set_description(Varlink *v, const char *d);
+
+/* Create a varlink server */
+int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags);
+VarlinkServer *varlink_server_ref(VarlinkServer *s);
+VarlinkServer *varlink_server_unref(VarlinkServer *s);
+
+/* Add addresses or fds to listen on */
+int varlink_server_listen_address(VarlinkServer *s, const char *address, mode_t mode);
+int varlink_server_listen_fd(VarlinkServer *s, int fd);
+int varlink_server_add_connection(VarlinkServer *s, int fd, Varlink **ret);
+
+/* Bind callbacks */
+int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMethod callback);
+int varlink_server_bind_method_many_internal(VarlinkServer *s, ...);
+#define varlink_server_bind_method_many(s, ...) varlink_server_bind_method_many_internal(s, __VA_ARGS__, NULL)
+int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect connect);
+
+void* varlink_server_set_userdata(VarlinkServer *s, void *userdata);
+void* varlink_server_get_userdata(VarlinkServer *s);
+
+int varlink_server_attach_event(VarlinkServer *v, sd_event *e, int64_t priority);
+int varlink_server_detach_event(VarlinkServer *v);
+sd_event *varlink_server_get_event(VarlinkServer *v);
+
+int varlink_server_shutdown(VarlinkServer *server);
+
+unsigned varlink_server_connections_max(VarlinkServer *s);
+unsigned varlink_server_connections_per_uid_max(VarlinkServer *s);
+
+int varlink_server_set_connections_per_uid_max(VarlinkServer *s, unsigned m);
+int varlink_server_set_connections_max(VarlinkServer *s, unsigned m);
+
+int varlink_server_set_description(VarlinkServer *s, const char *description);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref);
+
+#define VARLINK_ERROR_DISCONNECTED "io.systemd.Disconnected"
+#define VARLINK_ERROR_TIMEOUT "io.systemd.TimedOut"
+#define VARLINK_ERROR_PROTOCOL "io.systemd.Protocol"
+#define VARLINK_ERROR_SYSTEM "io.systemd.System"
+
+#define VARLINK_ERROR_INTERFACE_NOT_FOUND "org.varlink.service.InterfaceNotFound"
+#define VARLINK_ERROR_METHOD_NOT_FOUND "org.varlink.service.MethodNotFound"
+#define VARLINK_ERROR_METHOD_NOT_IMPLEMENTED "org.varlink.service.MethodNotImplemented"
+#define VARLINK_ERROR_INVALID_PARAMETER "org.varlink.service.InvalidParameter"
index d9b57e2c734ceb27e47989e79d988e440f9910f0..5dbfe8e4a1272b34bd528f74706b279bb99bb876 100644 (file)
@@ -38,6 +38,7 @@ enum {
         SD_DHCP_CLIENT_EVENT_IP_CHANGE          = 2,
         SD_DHCP_CLIENT_EVENT_EXPIRED            = 3,
         SD_DHCP_CLIENT_EVENT_RENEW              = 4,
+        SD_DHCP_CLIENT_EVENT_SELECTING          = 5,
 };
 
 enum {
@@ -98,7 +99,7 @@ enum {
 
 typedef struct sd_dhcp_client sd_dhcp_client;
 
-typedef void (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata);
+typedef int (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata);
 int sd_dhcp_client_set_callback(
                 sd_dhcp_client *client,
                 sd_dhcp_client_callback_t cb,
index 7bb8609376c43e6f42d6b1c1bcb2050ac0ff4a26..b14c92697b44803ad2d26d0409ff849de0d71684 100644 (file)
@@ -113,6 +113,7 @@ int sd_event_get_iteration(sd_event *e, uint64_t *ret);
 
 sd_event_source* sd_event_source_ref(sd_event_source *s);
 sd_event_source* sd_event_source_unref(sd_event_source *s);
+sd_event_source* sd_event_source_disable_unref(sd_event_source *s);
 
 sd_event *sd_event_source_get_event(sd_event_source *s);
 void* sd_event_source_get_userdata(sd_event_source *s);
@@ -149,6 +150,7 @@ int sd_event_source_set_floating(sd_event_source *s, int b);
 /* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_disable_unref);
 
 _SD_END_DECLARATIONS;
 
index d327b27308b68d56e139cf619ce698f7ccf9f4df..d27e0ad2019cb9b5016404ab1e24d3c360777e3b 100644 (file)
 ***/
 
 #include <inttypes.h>
-#include <linux/neighbour.h>
-#include <linux/rtnetlink.h>
 #include <net/ethernet.h>
 #include <netinet/ether.h>
 #include <netinet/in.h>
+#include <linux/neighbour.h>
+#include <linux/rtnetlink.h>
 
 #include "sd-event.h"
 
index e878d874afb2b923a4a65b0462d40775a50b4111..ee6cdb6d54b1ce83b74ce302da3e3f5647ffec04 100644 (file)
@@ -595,6 +595,10 @@ tests += [
           libmount,
           libblkid]],
 
+        [['src/test/test-varlink.c'],
+         [],
+         [threads]],
+
         [['src/test/test-cgroup-util.c'],
          [],
          []],
index 2dfdfe35ec740bb34643a5f78a49a4aa628ba6a5..e6b6d96d5a91b393ba4b2e2de80535d0e76b542e 100644 (file)
@@ -6,6 +6,7 @@
 #include "alloc-util.h"
 #include "macro.h"
 #include "memory-util.h"
+#include "tests.h"
 
 static void test_alloca(void) {
         static const uint8_t zero[997] = { };
@@ -106,11 +107,39 @@ static void test_bool_assign(void) {
         assert(!h);
 }
 
+static int cleanup_counter = 0;
+
+static void cleanup1(void *a) {
+        log_info("%s(%p)", __func__, a);
+        assert_se(++cleanup_counter == *(int*) a);
+}
+static void cleanup2(void *a) {
+        log_info("%s(%p)", __func__, a);
+        assert_se(++cleanup_counter == *(int*) a);
+}
+static void cleanup3(void *a) {
+        log_info("%s(%p)", __func__, a);
+        assert_se(++cleanup_counter == *(int*) a);
+}
+
+static void test_cleanup_order(void) {
+        _cleanup_(cleanup1) int x1 = 4, x2 = 3;
+        _cleanup_(cleanup3) int z = 2;
+        _cleanup_(cleanup2) int y = 1;
+        log_debug("x1: %p", &x1);
+        log_debug("x2: %p", &x2);
+        log_debug("y: %p", &y);
+        log_debug("z: %p", &z);
+}
+
 int main(int argc, char *argv[]) {
+        test_setup_logging(LOG_DEBUG);
+
         test_alloca();
         test_GREEDY_REALLOC();
         test_memdup_multiply_and_greedy_realloc();
         test_bool_assign();
+        test_cleanup_order();
 
         return 0;
 }
diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c
new file mode 100644 (file)
index 0000000..fbfc72c
--- /dev/null
@@ -0,0 +1,239 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+
+#include "sd-event.h"
+
+#include "fd-util.h"
+#include "json.h"
+#include "rm-rf.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "user-util.h"
+#include "varlink.h"
+
+/* Let's pick some high value, that is higher than the largest listen() backlog, but leaves enough room below
+   the typical RLIMIT_NOFILE value of 1024 so that we can process both sides of each socket in our
+   process. Or in other words: "OVERLOAD_CONNECTIONS * 2 + x < 1024" should hold, for some small x that
+   should cover any auxiliary fds, the listener server fds, stdin/stdout/stderr and whatever else. */
+#define OVERLOAD_CONNECTIONS 333
+
+static int n_done = 0;
+static int block_write_fd = -1;
+
+static int method_something(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+        _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL;
+        JsonVariant *a, *b;
+        intmax_t x, y;
+        int r;
+
+        a = json_variant_by_key(parameters, "a");
+        if (!a)
+                return varlink_error(link, "io.test.BadParameters", NULL);
+
+        x = json_variant_integer(a);
+
+        b = json_variant_by_key(parameters, "b");
+        if (!b)
+                return varlink_error(link, "io.test.BadParameters", NULL);
+
+        y = json_variant_integer(b);
+
+        r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("sum", JSON_BUILD_INTEGER(x + y))));
+        if (r < 0)
+                return r;
+
+        return varlink_reply(link, ret);
+}
+
+static int method_done(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+
+        if (++n_done == 2)
+                sd_event_exit(varlink_get_event(link), EXIT_FAILURE);
+
+        return 0;
+}
+
+static int reply(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata) {
+        JsonVariant *sum;
+
+        sum = json_variant_by_key(parameters, "sum");
+
+        assert_se(json_variant_integer(sum) == 7+22);
+
+        if (++n_done == 2)
+                sd_event_exit(varlink_get_event(link), EXIT_FAILURE);
+
+        return 0;
+}
+
+static int on_connect(VarlinkServer *s, Varlink *link, void *userdata) {
+        uid_t uid = UID_INVALID;
+
+        assert(s);
+        assert(link);
+
+        assert_se(varlink_get_peer_uid(link, &uid) >= 0);
+        assert_se(getuid() == uid);
+
+        return 0;
+}
+
+static int overload_reply(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata) {
+
+        /* This method call reply should always be called with a disconnection, since the method call should
+         * be talking to an overloaded server */
+
+        log_debug("Over reply triggered with error: %s", strna(error_id));
+        assert_se(streq(error_id, VARLINK_ERROR_DISCONNECTED));
+        sd_event_exit(varlink_get_event(link), 0);
+
+        return 0;
+}
+
+static void flood_test(const char *address) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL;
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+        _cleanup_free_ Varlink **connections = NULL;
+        size_t k;
+        char x = 'x';
+
+        log_debug("Flooding server...");
+
+        /* Block the main event loop while we flood */
+        assert_se(write(block_write_fd, &x, sizeof(x)) == sizeof(x));
+
+        assert_se(sd_event_default(&e) >= 0);
+
+        /* Flood the server with connections */
+        assert_se(connections = new0(Varlink*, OVERLOAD_CONNECTIONS));
+        for (k = 0; k < OVERLOAD_CONNECTIONS; k++) {
+                _cleanup_free_ char *t = NULL;
+                log_debug("connection %zu", k);
+                assert_se(varlink_connect_address(connections + k, address) >= 0);
+
+                assert_se(asprintf(&t, "flood-%zu", k) >= 0);
+                assert_se(varlink_set_description(connections[k], t) >= 0);
+                assert_se(varlink_attach_event(connections[k], e, k) >= 0);
+                assert_se(varlink_sendb(connections[k], "io.test.Rubbish", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("id", JSON_BUILD_INTEGER(k)))) >= 0);
+        }
+
+        /* Then, create one more, which should fail */
+        log_debug("Creating overload connection...");
+        assert_se(varlink_connect_address(&c, address) >= 0);
+        assert_se(varlink_set_description(c, "overload-client") >= 0);
+        assert_se(varlink_attach_event(c, e, k) >= 0);
+        assert_se(varlink_bind_reply(c, overload_reply) >= 0);
+        assert_se(varlink_invokeb(c, "io.test.Overload", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("foo", JSON_BUILD_STRING("bar")))) >= 0);
+
+        /* Unblock it */
+        log_debug("Unblocking server...");
+        block_write_fd = safe_close(block_write_fd);
+
+        /* This loop will terminate as soon as the overload reply callback is called */
+        assert_se(sd_event_loop(e) >= 0);
+
+        /* And close all connections again */
+        for (k = 0; k < OVERLOAD_CONNECTIONS; k++)
+                connections[k] = varlink_unref(connections[k]);
+}
+
+static void *thread(void *arg) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *i = NULL;
+        JsonVariant *o = NULL;
+        const char *e;
+
+        assert_se(json_build(&i, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("a", JSON_BUILD_INTEGER(88)),
+                                                   JSON_BUILD_PAIR("b", JSON_BUILD_INTEGER(99)))) >= 0);
+
+        assert_se(varlink_connect_address(&c, arg) >= 0);
+        assert_se(varlink_set_description(c, "thread-client") >= 0);
+
+        assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e, NULL) >= 0);
+        assert_se(json_variant_integer(json_variant_by_key(o, "sum")) == 88 + 99);
+        assert_se(!e);
+
+        assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0);
+        assert_se(streq_ptr(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist"));
+        assert_se(streq(e, VARLINK_ERROR_METHOD_NOT_FOUND));
+
+        flood_test(arg);
+
+        assert_se(varlink_send(c, "io.test.Done", NULL) >= 0);
+
+        return NULL;
+}
+
+static int block_fd_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        char c;
+
+        assert_se(fd_nonblock(fd, false) >= 0);
+
+        assert_se(read(fd, &c, sizeof(c)) == sizeof(c));
+        /* When a character is written to this pipe we'll block until the pipe is closed. */
+
+        assert_se(read(fd, &c, sizeof(c)) == 0);
+
+        assert_se(fd_nonblock(fd, true) >= 0);
+
+        assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0);
+
+        return 0;
+}
+
+int main(int argc, char *argv[]) {
+        _cleanup_(sd_event_source_unrefp) sd_event_source *block_event = NULL;
+        _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
+        _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL;
+        _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+        _cleanup_(close_pairp) int block_fds[2] = { -1, -1 };
+        pthread_t t;
+        const char *sp;
+
+        log_set_max_level(LOG_DEBUG);
+        log_open();
+
+        assert_se(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir) >= 0);
+        sp = strjoina(tmpdir, "/socket");
+
+        assert_se(sd_event_default(&e) >= 0);
+
+        assert_se(pipe2(block_fds, O_NONBLOCK|O_CLOEXEC) >= 0);
+        assert_se(sd_event_add_io(e, &block_event, block_fds[0], EPOLLIN, block_fd_handler, NULL) >= 0);
+        assert_se(sd_event_source_set_priority(block_event, SD_EVENT_PRIORITY_IMPORTANT) >= 0);
+        block_write_fd = TAKE_FD(block_fds[1]);
+
+        assert_se(varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID) >= 0);
+        assert_se(varlink_server_set_description(s, "our-server") >= 0);
+
+        assert_se(varlink_server_bind_method(s, "io.test.DoSomething", method_something) >= 0);
+        assert_se(varlink_server_bind_method(s, "io.test.Done", method_done) >= 0);
+        assert_se(varlink_server_bind_connect(s, on_connect) >= 0);
+        assert_se(varlink_server_listen_address(s, sp, 0600) >= 0);
+        assert_se(varlink_server_attach_event(s, e, 0) >= 0);
+        assert_se(varlink_server_set_connections_max(s, OVERLOAD_CONNECTIONS) >= 0);
+
+        assert_se(varlink_connect_address(&c, sp) >= 0);
+        assert_se(varlink_set_description(c, "main-client") >= 0);
+        assert_se(varlink_bind_reply(c, reply) >= 0);
+
+        assert_se(json_build(&v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("a", JSON_BUILD_INTEGER(7)),
+                                                   JSON_BUILD_PAIR("b", JSON_BUILD_INTEGER(22)))) >= 0);
+
+        assert_se(varlink_invoke(c, "io.test.DoSomething", v) >= 0);
+
+        assert_se(varlink_attach_event(c, e, 0) >= 0);
+
+        assert_se(pthread_create(&t, NULL, thread, (void*) sp) == 0);
+
+        assert_se(sd_event_loop(e) >= 0);
+
+        assert_se(pthread_join(t, NULL) == 0);
+
+        return 0;
+}
index 9296c16c498df2f0606b036b6b47c540458ff5be..97e8e4a58026dcad1fbd90fbb2f022ae09b27f7f 100644 (file)
@@ -10,6 +10,9 @@ MulticastToUnicast=
 MulticastFlood=
 NeighborSuppression=
 Learning=
+ProxyARP=
+ProxyARPWiFi=
+MulticastRouter=
 [Match]
 KernelVersion=
 Type=
@@ -59,6 +62,7 @@ ClientIdentifier=
 ListenPort=
 UseTimezone=
 RouteTable=
+BlackList=
 [Route]
 Destination=
 Protocol=
diff --git a/test/fuzz/fuzz-varlink/array b/test/fuzz/fuzz-varlink/array
new file mode 100644 (file)
index 0000000..f3ee40b
Binary files /dev/null and b/test/fuzz/fuzz-varlink/array differ
diff --git a/test/fuzz/fuzz-varlink/do-something b/test/fuzz/fuzz-varlink/do-something
new file mode 100644 (file)
index 0000000..3b124cb
Binary files /dev/null and b/test/fuzz/fuzz-varlink/do-something differ
diff --git a/test/fuzz/fuzz-varlink/huge-method b/test/fuzz/fuzz-varlink/huge-method
new file mode 100644 (file)
index 0000000..a480e41
--- /dev/null
@@ -0,0 +1 @@
+{"method":"                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                "}\0
\ No newline at end of file
diff --git a/test/fuzz/fuzz-varlink/method-call b/test/fuzz/fuzz-varlink/method-call
new file mode 100644 (file)
index 0000000..8654a7c
Binary files /dev/null and b/test/fuzz/fuzz-varlink/method-call differ
diff --git a/test/fuzz/fuzz-varlink/method-error b/test/fuzz/fuzz-varlink/method-error
new file mode 100644 (file)
index 0000000..9ce68d8
Binary files /dev/null and b/test/fuzz/fuzz-varlink/method-error differ
diff --git a/test/fuzz/fuzz-varlink/method-reply b/test/fuzz/fuzz-varlink/method-reply
new file mode 100644 (file)
index 0000000..cd4bd94
Binary files /dev/null and b/test/fuzz/fuzz-varlink/method-reply differ
diff --git a/test/fuzz/fuzz-varlink/timeout-d8a88bf4adea54537d21e3afb396e1a55c5b58bf b/test/fuzz/fuzz-varlink/timeout-d8a88bf4adea54537d21e3afb396e1a55c5b58bf
new file mode 100644 (file)
index 0000000..b0d8618
Binary files /dev/null and b/test/fuzz/fuzz-varlink/timeout-d8a88bf4adea54537d21e3afb396e1a55c5b58bf differ
index 50c6e9690251a6c2b5c2a347ec4e0ce5a99c7799..bfd6b6662a680b808a38be08b1f5f66c39323379 100644 (file)
@@ -50,7 +50,7 @@ IS_BUILT_WITH_ASAN=$(is_built_with_asan && echo yes || echo no)
 
 if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
     STRIP_BINARIES=no
-    SKIP_INITRD=yes
+    SKIP_INITRD="${SKIP_INITRD:-yes}"
     PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
     QEMU_MEM="1536M"
     QEMU_SMP=4
@@ -386,6 +386,18 @@ printf "[Service]\nEnvironment=ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS:log_path=/sys
 mkdir -p /etc/systemd/system/systemd-hwdb-update.service.d
 printf "[Unit]\nConditionVirtualization=container\n\n[Service]\nTimeoutSec=180s\n" >/etc/systemd/system/systemd-hwdb-update.service.d/env-override.conf
 
+# The 'mount' utility doesn't behave well under libasan, causing unexpected
+# fails during boot and subsequent test results check:
+# bash-5.0# mount -o remount,rw -v /
+# mount: /dev/sda1 mounted on /.
+# bash-5.0# echo \$?
+# 1
+# Let's workaround this by clearing the previously set LD_PRELOAD env variable,
+# so the libasan library is not loaded for this particular service
+REMOUNTFS_CONF_DIR=/etc/systemd/system/systemd-remount-fs.service.d
+mkdir -p "\$REMOUNTFS_CONF_DIR"
+printf "[Service]\nUnsetEnvironment=LD_PRELOAD\n" >"\$REMOUNTFS_CONF_DIR/env.conf"
+
 export ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS:log_path=/systemd.asan.log UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS
 exec  $ROOTLIBDIR/systemd "\$@"
 EOF
index bacfe51d6f3f450d9382a4428c8296ff13fd0eee..29b006cba5775ac2fb42859e4d4505d923dac776 100644 (file)
@@ -18,6 +18,7 @@ RequiresMountsFor=/var/log/journal
 
 [Service]
 ExecStart=@rootbindir@/journalctl --flush
+ExecStop=@rootbindir@/journalctl --smart-relinquish-var
 Type=oneshot
 RemainAfterExit=yes
 TimeoutSec=90s