]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
notify: add support for sending fds with notification messages
authorLennart Poettering <lennart@poettering.net>
Tue, 28 Mar 2023 09:17:44 +0000 (11:17 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 29 Mar 2023 17:09:10 +0000 (19:09 +0200)
This exposes the fd passing we support via sd_pid_notify_with_fds() also
via the command line tool systemd-notify.

man/systemd-notify.xml
src/notify/notify.c

index 5a154686f5f7b305ca9676db8c5de55720a70eef..1b469fe85c4a9a628130cd3e53ac2202768a874d 100644 (file)
         escaped as <literal>\;</literal>.</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>
index 7320ffebdeffe7a39e6e596f913f585810c7bdbf..8489b8387309e36f9d043eaa1d448384f0095798 100644 (file)
@@ -11,6 +11,8 @@
 #include "alloc-util.h"
 #include "build.h"
 #include "env-util.h"
+#include "fd-util.h"
+#include "fdset.h"
 #include "format-util.h"
 #include "log.h"
 #include "main-func.h"
@@ -33,9 +35,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;
@@ -60,6 +66,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,
@@ -103,6 +111,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[] = {
@@ -117,9 +127,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;
 
@@ -198,6 +211,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(&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;
 
@@ -212,11 +279,15 @@ static int parse_argv(int argc, char *argv[]) {
             !arg_reloading &&
             !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;
 
@@ -243,13 +314,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, *monotonic_usec = NULL;
+        _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL, *fdn = NULL;
         _cleanup_strv_free_ char **final_env = NULL;
-        char* our_env[7];
+        char* our_env[9];
         size_t i = 0;
         pid_t source_pid;
         int r;
@@ -302,6 +376,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);
@@ -338,13 +424,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)