From: Michael Vogt Date: Thu, 2 Apr 2026 10:16:35 +0000 (+0200) Subject: sd-varlink: extract varlink_handle_upgrade_fds() helper X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=82b04e7f8b7a5b4ee0cc50d92a830f8716ef2f77;p=thirdparty%2Fsystemd.git sd-varlink: extract varlink_handle_upgrade_fds() helper 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(). --- diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 90be0177054..25e67b4b748 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -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);