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.
This commit is contained in:
Lennart Poettering 2023-03-28 11:17:44 +02:00
parent e829f28c1b
commit 6e4a324574
2 changed files with 128 additions and 4 deletions

View file

@ -191,6 +191,29 @@
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>

View 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)