]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-varlink: extract varlink_handle_upgrade_fds() helper
authorMichael Vogt <michael@amutable.com>
Thu, 2 Apr 2026 10:16:35 +0000 (12:16 +0200)
committerMichael Vogt <michael@amutable.com>
Thu, 9 Apr 2026 06:50:40 +0000 (08:50 +0200)
Extract the fd-handling logic from sd_varlink_call_and_upgrade() into a
shared static helper so that it can be reused by the upcoming server-side
sd_varlink_reply_and_upgrade().

src/libsystemd/sd-varlink/sd-varlink.c

index 90be0177054cc04ed35fdee15926c9f6bbaa76fc..25e67b4b7484958af5959659e971d27cb4600e05 100644 (file)
@@ -2385,6 +2385,58 @@ _public_ int sd_varlink_call(
         return sd_varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
 }
 
+static int varlink_handle_upgrade_fds(sd_varlink *v, int *ret_input_fd, int *ret_output_fd) {
+        int r;
+
+        assert(v);
+        assert(ret_input_fd || ret_output_fd);
+
+        /* Ensure no post-upgrade data was consumed into our input buffer (we ensure this via MSG_PEEK or
+         * byte-to-byte) and refuse the upgrade rather than silently losing the data. */
+        if (v->input_buffer_size != 0)
+                return varlink_log_errno(v, SYNTHETIC_ERRNO(EPROTO),
+                                         "Unexpected buffered data during protocol upgrade, refusing.");
+
+        /* Pass the connection fds to the caller, it owns them now. Reset to blocking mode
+         * since callers of the upgraded protocol will generally expect normal blocking
+         * semantics. */
+        r = fd_nonblock(v->input_fd, false);
+        if (r < 0)
+                return varlink_log_errno(v, r, "Failed to set input fd to blocking mode: %m");
+        if (v->input_fd != v->output_fd) {
+                r = fd_nonblock(v->output_fd, false);
+                if (r < 0)
+                        return varlink_log_errno(v, r, "Failed to set output fd to blocking mode: %m");
+        }
+
+        /* Hand out the fds to the caller. When the caller doesn't want one direction, shut it
+         * down: but avoid closing the underlying fd if the other direction still needs it
+         * (i.e. when input_fd == output_fd). */
+        bool same_fd = v->input_fd == v->output_fd;
+
+        if (ret_input_fd)
+                *ret_input_fd = TAKE_FD(v->input_fd);
+        else {
+                (void) shutdown(v->input_fd, SHUT_RD);
+                if (same_fd && ret_output_fd)
+                        TAKE_FD(v->input_fd); /* don't close yet, output branch needs it */
+                else
+                        v->input_fd = safe_close(v->input_fd);
+        }
+
+        if (ret_output_fd)
+                *ret_output_fd = TAKE_FD(v->output_fd);
+        else {
+                (void) shutdown(v->output_fd, SHUT_WR);
+                if (same_fd && ret_input_fd)
+                        TAKE_FD(v->output_fd);
+                else
+                        v->output_fd = safe_close(v->output_fd);
+        }
+
+        return 0;
+}
+
 _public_ int sd_varlink_call_and_upgrade(
                 sd_varlink *v,
                 const char *method,
@@ -2436,45 +2488,12 @@ _public_ int sd_varlink_call_and_upgrade(
                 goto finish;
         }
 
-        /* Pass the connection fds to the caller, it owns them now. Reset to blocking mode
-         * since callers of the upgraded protocol will generally expect normal blocking
-         * semantics. */
-        r = fd_nonblock(v->input_fd, false);
+        /* Even if setting up the fds fails we must disconnect: the server already accepted the
+         * upgrade, so the other side is speaking raw protocol while we expect JSON. */
+        r = varlink_handle_upgrade_fds(v, ret_input_fd, ret_output_fd);
         if (r < 0) {
-                varlink_log_errno(v, r, "Failed to set input fd to blocking mode: %m");
-                goto disconnect;
-        }
-        if (v->input_fd != v->output_fd) {
-                r = fd_nonblock(v->output_fd, false);
-                if (r < 0) {
-                        varlink_log_errno(v, r, "Failed to set output fd to blocking mode: %m");
-                        goto disconnect;
-                }
-        }
-
-        /* Hand out the fds to the caller. When the caller doesn't want one direction, shut it
-         * down: but avoid closing the underlying fd if the other direction still needs it
-         * (i.e. when input_fd == output_fd). */
-        bool same_fd = v->input_fd == v->output_fd;
-
-        if (ret_input_fd)
-                *ret_input_fd = TAKE_FD(v->input_fd);
-        else {
-                (void) shutdown(v->input_fd, SHUT_RD);
-                if (same_fd && ret_output_fd)
-                        TAKE_FD(v->input_fd); /* don't close yet, output branch needs it */
-                else
-                        v->input_fd = safe_close(v->input_fd);
-        }
-
-        if (ret_output_fd)
-                *ret_output_fd = TAKE_FD(v->output_fd);
-        else {
-                (void) shutdown(v->output_fd, SHUT_WR);
-                if (same_fd && ret_input_fd)
-                        TAKE_FD(v->output_fd);
-                else
-                        v->output_fd = safe_close(v->output_fd);
+                varlink_set_state(v, VARLINK_DISCONNECTED);
+                goto finish;
         }
 
         varlink_set_state(v, VARLINK_DISCONNECTED);
@@ -2488,10 +2507,6 @@ _public_ int sd_varlink_call_and_upgrade(
 
         return 1;
 
-disconnect:
-        /* If we fail after the server already accepted the upgrade, nothing can be done but disconnect.
-         * The other side is speaking raw protocol while we expect JSON. */
-        varlink_set_state(v, VARLINK_DISCONNECTED);
 finish:
         v->protocol_upgrade = false;
         assert(v->n_pending == 1);