From 6e4a324574d5f1b2296799324dbdb54326078233 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 28 Mar 2023 11:17:44 +0200
Subject: [PATCH] notify: add support for sending fds with notification
 messages

This exposes the fd passing we support via sd_pid_notify_with_fds() also
via the command line tool systemd-notify.

Conflict:Context Adaptation. fdset_new_fill adaptation.
Reference:https://github.com/systemd/systemd/commit/6e4a324574d5f1b2296799324dbdb54326078233

---
 man/systemd-notify.xml |  23 +++++++++
 src/notify/notify.c    | 109 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 128 insertions(+), 4 deletions(-)

diff --git a/man/systemd-notify.xml b/man/systemd-notify.xml
index 3fed92e..586d09e 100644
--- a/man/systemd-notify.xml
+++ b/man/systemd-notify.xml
@@ -143,6 +143,29 @@
         this option set is prone to race conditions in all other cases.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--fd=</option></term>
+
+        <listitem><para>Send a file descriptor along with the notification message. This is useful when
+        invoked in services that have the <varname>FileDescriptorStoreMax=</varname> setting enabled, see
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for details. The specified file descriptor must be passed to <command>systemd-notify</command> when
+        invoked. This option may be used multiple times to pass multiple file descriptors in a single
+        notification message.</para>
+
+        <para>To use this functionality from a <command>bash</command> shell, use an expression like the following:</para>
+        <programlisting>systemd-notify --fd=4 --fd=5 4&lt;/some/file 5&lt;/some/other/file</programlisting></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--fdname=</option></term>
+
+        <listitem><para>Set a name to assign to the file descriptors passed via <option>--fd=</option> (see
+        above). This controls the <literal>FDNAME=</literal> field. This setting may only be specified once,
+        and applies to all file descriptors passed. Invoke this tool multiple times in case multiple file
+        descriptors with different file descriptor names shall be submitted.</para></listitem>
+      </varlistentry>
+
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
     </variablelist>
diff --git a/src/notify/notify.c b/src/notify/notify.c
index 5afea7f..ad0ae89 100644
--- a/src/notify/notify.c
+++ b/src/notify/notify.c
@@ -10,6 +10,8 @@
 
 #include "alloc-util.h"
 #include "env-util.h"
+#include "fd-util.h"
+#include "fdset.h"
 #include "format-util.h"
 #include "log.h"
 #include "main-func.h"
@@ -31,9 +33,13 @@ static gid_t arg_gid = GID_INVALID;
 static bool arg_no_block = false;
 static char **arg_env = NULL;
 static char **arg_exec = NULL;
+static FDSet *arg_fds = NULL;
+static char *arg_fdname = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_exec, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep);
 
 static int help(void) {
         _cleanup_free_ char *link = NULL;
@@ -55,6 +61,8 @@ static int help(void) {
                "     --booted          Check if the system was booted up with systemd\n"
                "     --no-block        Do not wait until operation finished\n"
                "     --exec            Execute command line separated by ';' once done\n"
+               "     --fd=FD           Pass specified file descriptor with along with message\n"
+               "     --fdname=NAME     Name to assign to passed file descriptor(s)\n"
                "\nSee the %s for details.\n",
                program_invocation_short_name,
                program_invocation_short_name,
@@ -96,6 +104,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UID,
                 ARG_NO_BLOCK,
                 ARG_EXEC,
+                ARG_FD,
+                ARG_FDNAME,
         };
 
         static const struct option options[] = {
@@ -108,9 +118,12 @@ static int parse_argv(int argc, char *argv[]) {
                 { "uid",       required_argument, NULL, ARG_UID       },
                 { "no-block",  no_argument,       NULL, ARG_NO_BLOCK  },
                 { "exec",      no_argument,       NULL, ARG_EXEC      },
+                { "fd",        required_argument, NULL, ARG_FD        },
+                { "fdname",    required_argument, NULL, ARG_FDNAME    },
                 {}
         };
 
+        _cleanup_(fdset_freep) FDSet *passed = NULL;
         bool do_exec = false;
         int c, r, n_env;
 
@@ -181,6 +194,60 @@ static int parse_argv(int argc, char *argv[]) {
                         do_exec = true;
                         break;
 
+                case ARG_FD: {
+                        _cleanup_close_ int owned_fd = -EBADF;
+                        int fdnr;
+
+                        r = safe_atoi(optarg, &fdnr);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse file descriptor: %s", optarg);
+                        if (fdnr < 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "File descriptor can't be negative: %i", fdnr);
+
+                        if (!passed) {
+                                /* Take possession of all passed fds */
+                                r = fdset_new_fill(/* filter_cloexec= */ 0, &passed);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to take possession of passed file descriptors: %m");
+
+                                r = fdset_cloexec(passed, true);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to enable O_CLOEXEC for passed file descriptors: %m");
+                        }
+
+                        if (fdnr < 3) {
+                                /* For stdin/stdout/stderr we want to keep the fd, too, hence make a copy */
+                                owned_fd = fcntl(fdnr, F_DUPFD_CLOEXEC, 3);
+                                if (owned_fd < 0)
+                                        return log_error_errno(errno, "Failed to duplicate file descriptor: %m");
+                        } else {
+                                /* Otherwise, move the fd over */
+                                owned_fd = fdset_remove(passed, fdnr);
+                                if (owned_fd < 0)
+                                        return log_error_errno(owned_fd, "Specified file descriptor '%i' not passed or specified more than once: %m", fdnr);
+                        }
+
+                        if (!arg_fds) {
+                                arg_fds = fdset_new();
+                                if (!arg_fds)
+                                        return log_oom();
+                        }
+
+                        r = fdset_consume(arg_fds, TAKE_FD(owned_fd));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to add file descriptor to set: %m");
+                        break;
+                }
+
+                case ARG_FDNAME:
+                        if (!fdname_is_valid(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", optarg);
+
+                        if (free_and_strdup(&arg_fdname, optarg) < 0)
+                                return log_oom();
+
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -193,11 +260,15 @@ static int parse_argv(int argc, char *argv[]) {
             !arg_ready &&
             !arg_status &&
             !arg_pid &&
-            !arg_booted) {
+            !arg_booted &&
+            fdset_isempty(arg_fds)) {
                 help();
                 return -EINVAL;
         }
 
+        if (arg_fdname && fdset_isempty(arg_fds))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= set, refusing.");
+
         if (do_exec) {
                 int i;
 
@@ -224,13 +295,16 @@ static int parse_argv(int argc, char *argv[]) {
                         return log_oom();
         }
 
+        if (!fdset_isempty(passed))
+                log_warning("Warning: %u more file descriptors passed than referenced with --fd=.", fdset_size(passed));
+
         return 1;
 }
 
 static int run(int argc, char* argv[]) {
-        _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL;
+        _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *fdn = NULL;
         _cleanup_strv_free_ char **final_env = NULL;
-        char* our_env[4];
+        char* our_env[6];
         unsigned i = 0;
         pid_t source_pid;
         int r;
@@ -271,6 +345,18 @@ static int run(int argc, char* argv[]) {
                 our_env[i++] = cpid;
         }
 
+        if (!fdset_isempty(arg_fds)) {
+                our_env[i++] = (char*) "FDSTORE=1";
+
+                if (arg_fdname) {
+                        fdn = strjoin("FDNAME=", arg_fdname);
+                        if (!fdn)
+                                return log_oom();
+
+                        our_env[i++] = fdn;
+                }
+        }
+
         our_env[i++] = NULL;
 
         final_env = strv_env_merge(our_env, arg_env);
@@ -307,13 +393,28 @@ static int run(int argc, char* argv[]) {
                                                   * or the service manager itself */
                         source_pid = 0;
         }
-        r = sd_pid_notify(source_pid, false, n);
+
+        if (fdset_isempty(arg_fds))
+                r = sd_pid_notify(source_pid, /* unset_environment= */ false, n);
+        else {
+                _cleanup_free_ int *a = NULL;
+                int k;
+
+                k = fdset_to_array(arg_fds, &a);
+                if (k < 0)
+                        return log_error_errno(k, "Failed to convert file descriptor set to array: %m");
+
+                r = sd_pid_notify_with_fds(source_pid, /* unset_environment= */ false, n, a, k);
+
+        }
         if (r < 0)
                 return log_error_errno(r, "Failed to notify init system: %m");
         if (r == 0)
                 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                        "No status data could be sent: $NOTIFY_SOCKET was not set");
 
+        arg_fds = fdset_free(arg_fds); /* Close before we execute anything */
+
         if (!arg_no_block) {
                 r = sd_notify_barrier(0, 5 * USEC_PER_SEC);
                 if (r < 0)
-- 
2.33.0

