core: add support for naming file descriptors passed using socket activation

This adds support for naming file descriptors passed using socket
activation. The names are passed in a new $LISTEN_FDNAMES= environment
variable, that matches the existign $LISTEN_FDS= one and contains a
colon-separated list of names.

This also adds support for naming fds submitted to the per-service fd
store using FDNAME= in the sd_notify() message.

This also adds a new FileDescriptorName= setting for socket unit files
to set the name for fds created by socket units.

This also adds a new call sd_listen_fds_with_names(), that is similar to
sd_listen_fds(), but also returns the names of the fds.

systemd-activate gained the new --fdname= switch to specify a name for
testing socket activation.

This is based on #1247 by Maciej Wereski.

Fixes #1247.
This commit is contained in:
Lennart Poettering 2015-10-04 17:36:19 +02:00
parent 79c7626d1f
commit 8dd4c05b54
26 changed files with 680 additions and 173 deletions

View File

@ -355,6 +355,7 @@ MANPAGES_ALIAS += \
man/sd_journal_set_data_threshold.3 \
man/sd_journal_test_cursor.3 \
man/sd_journal_wait.3 \
man/sd_listen_fds_with_names.3 \
man/sd_machine_get_ifindices.3 \
man/sd_notifyf.3 \
man/sd_pid_notify.3 \
@ -643,6 +644,7 @@ man/sd_journal_sendv.3: man/sd_journal_print.3
man/sd_journal_set_data_threshold.3: man/sd_journal_get_data.3
man/sd_journal_test_cursor.3: man/sd_journal_get_cursor.3
man/sd_journal_wait.3: man/sd_journal_get_fd.3
man/sd_listen_fds_with_names.3: man/sd_listen_fds.3
man/sd_machine_get_ifindices.3: man/sd_machine_get_class.3
man/sd_notifyf.3: man/sd_notify.3
man/sd_pid_notify.3: man/sd_notify.3
@ -1319,6 +1321,9 @@ man/sd_journal_test_cursor.html: man/sd_journal_get_cursor.html
man/sd_journal_wait.html: man/sd_journal_get_fd.html
$(html-alias)
man/sd_listen_fds_with_names.html: man/sd_listen_fds.html
$(html-alias)
man/sd_machine_get_ifindices.html: man/sd_machine_get_class.html
$(html-alias)

View File

@ -1,4 +1,4 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
@ -45,6 +45,7 @@
<refnamediv>
<refname>sd_listen_fds</refname>
<refname>sd_listen_fds_with_names</refname>
<refname>SD_LISTEN_FDS_START</refname>
<refpurpose>Check for file descriptors passed by the system manager</refpurpose>
</refnamediv>
@ -59,23 +60,26 @@
<funcdef>int <function>sd_listen_fds</function></funcdef>
<paramdef>int <parameter>unset_environment</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_listen_fds_with_names</function></funcdef>
<paramdef>int <parameter>unset_environment</parameter></paramdef>
<paramdef>char*** <parameter>names</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><function>sd_listen_fds()</function> shall be called by a
daemon to check for file descriptors passed by the init system as
part of the socket-based activation logic.</para>
<para>If the <parameter>unset_environment</parameter> parameter is
non-zero, <function>sd_listen_fds()</function> will unset the
<varname>$LISTEN_FDS</varname> and <varname>$LISTEN_PID</varname>
environment variables before returning (regardless of whether the
function call itself succeeded or not). Further calls to
<function>sd_listen_fds()</function> will then fail, but the
variables are no longer inherited by child processes.</para>
<para><function>sd_listen_fds()</function> may be invoked by a
daemon to check for file descriptors passed by the service manager as
part of the socket-based activation logic. It returns the number
of received file descriptors. If no file descriptors have been
received zero is returned. The first file descriptor may be found
at file descriptor number 3
(i.e. <constant>SD_LISTEN_FDS_START</constant>), the remaining
descriptors follow at 4, 5, 6, ..., if any.</para>
<para>If a daemon receives more than one file descriptor, they
will be passed in the same order as configured in the systemd
@ -108,12 +112,86 @@
<literal>FDSTORE=1</literal> messages, these file descriptors are
passed last, in arbitrary order, and with duplicates
removed.</para>
<para>If the <parameter>unset_environment</parameter> parameter is
non-zero, <function>sd_listen_fds()</function> will unset the
<varname>$LISTEN_FDS</varname>, <varname>$LISTEN_PID</varname> and
<varname>$LISTEN_FDNAMES</varname> environment variables before
returning (regardless of whether the function call itself
succeeded or not). Further calls to
<function>sd_listen_fds()</function> will then return zero, but the
variables are no longer inherited by child processes.</para>
<para><function>sd_listen_fds_with_names()</function> is like
<function>sd_listen_fds()</function> but optionally also returns
an array of strings with identification names for the passed file
descriptors, if that is available, and the
<parameter>names</parameter> parameter is non-NULL. This
information is read from the <varname>$LISTEN_FDNAMES</varname>
variable, which may contain a colon-separated list of names. For
socket-activated services, these names may be configured with the
<varname>FileDescriptorName=</varname> setting in socket unit
files, see
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. For file descriptors pushed into the file descriptor
store (see above) the name is set via the
<varname>FDNAME=</varname> field transmitted via
<function>sd_pid_notify_with_fds()</function>. The primary usecase
for these names are services which accept a variety of file
descriptors which are not recognizable with functions like
<function>sd_is_socket()</function> alone, and thus require
identification via a name. It is recommended to rely on named file
descriptors only if identification via
<function>sd_is_socket()</function> and related calls is not
sufficient. Note that the names used are not unique in any
way. The returned array of strings has as many entries as file
descriptors has been received, plus a final NULL pointer
terminating the array. The caller needs to free the array itself
and each of its elements with libc's <varname>free()</varname>
call after use. If the <parameter>names</parameter> parameter is
NULL the call is entirely equivalent to
<function>sd_listen_fds()</function>.</para>
<para>Under specific conditions the following automatic file
descriptor names are returned:
<table>
<title>
<command>Special names</command>
</title>
<tgroup cols='2'>
<thead>
<row>
<entry>Name</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>unknown</literal></entry>
<entry>The process received no name for the specific file descriptor from the service manager.</entry>
</row>
<row>
<entry><literal>stored</literal></entry>
<entry>The file descriptor originates in the service manager's per-service file descriptor store, and the <varname>FDNAME=</varname> field was absent when the file descriptor was submitted to the service manager.</entry>
</row>
<row>
<entry><literal>connection</literal></entry>
<entry>The service was activated in per-connection style using <varname>Accept=yes</varname> in the socket unit file, and the file descriptor is the connection socket.</entry>
</row>
</tbody>
</tgroup>
</table>
</para>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>On failure, this call returns a negative errno-style error
<para>On failure, these calls returns a negative errno-style error
code. If
<varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname> was
not set or was not correctly set for this daemon and hence no file
@ -128,13 +206,16 @@
<xi:include href="libsystemd-pkgconfig.xml" xpointer="pkgconfig-text"/>
<para>Internally, this function checks whether the
<varname>$LISTEN_PID</varname> environment variable equals the
daemon PID. If not, it returns immediately. Otherwise, it parses
the number passed in the <varname>$LISTEN_FDS</varname>
<para>Internally, <function>sd_listen_fds()</function> checks
whether the <varname>$LISTEN_PID</varname> environment variable
equals the daemon PID. If not, it returns immediately. Otherwise,
it parses the number passed in the <varname>$LISTEN_FDS</varname>
environment variable, then sets the FD_CLOEXEC flag for the parsed
number of file descriptors starting from SD_LISTEN_FDS_START.
Finally, it returns the parsed number.</para>
Finally, it returns the parsed
number. <function>sd_listen_fds_with_names()</function> does the
same but also parses <varname>$LISTEN_FDNAMES</varname> if
set.</para>
</refsect1>
<refsect1>
@ -144,15 +225,14 @@
<varlistentry>
<term><varname>$LISTEN_PID</varname></term>
<term><varname>$LISTEN_FDS</varname></term>
<term><varname>$LISTEN_FDNAMES</varname></term>
<listitem><para>Set by the init system
for supervised processes that use
socket-based activation. This
environment variable specifies the
data
<function>sd_listen_fds()</function>
parses. See above for
details.</para></listitem>
<listitem><para>Set by the service manager for supervised
processes that use socket-based activation. This environment
variable specifies the data
<function>sd_listen_fds()</function> and
<function>sd_listen_fds_with_names()</function> parses. See
above for details.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -167,6 +247,7 @@
<citerefentry><refentrytitle>sd_is_socket</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_is_socket_inet</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_is_socket_unix</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_pid_notify_with_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>

View File

@ -1,4 +1,4 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
@ -229,6 +229,27 @@
below.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDNAME=...</term>
<listitem><para>When used in combination with
<varname>FDSTORE=1</varname> specifies a name for the
submitted file descriptors. This name is passed to the service
during activation, and may be queried using
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>. File
descriptors submitted without this field set, will implicitly
get the name <literal>stored</literal> assigned. Note that if
multiple file descriptors are submitted at once the specified
name will be assigned to all of them. In order to assign
different names to submitted file descriptors, submit them in
seperate invocations of
<function>sd_pid_notify_with_fds()</function>. The name may
consist of any ASCII characters, but must not contain control
characters or <literal>:</literal>. It may not be longer than
255 characters. If a submitted name does not follow these
restrictions it is ignored.</para></listitem>
</varlistentry>
</variablelist>
<para>It is recommended to prefix variable names that are not
@ -358,7 +379,7 @@
in order to continue operation after a service restart without
losing state use <literal>FDSTORE=1</literal>:</para>
<programlisting>sd_pid_notify_with_fds(0, 0, "FDSTORE=1", &amp;fd, 1);</programlisting>
<programlisting>sd_pid_notify_with_fds(0, 0, "FDSTORE=1\nFDNAME=foobar", &amp;fd, 1);</programlisting>
</example>
</refsect1>
@ -367,9 +388,11 @@
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_watchdog_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>daemon</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_watchdog_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -115,6 +115,16 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--fdname=</option><replaceable>NAME</replaceable></term>
<listitem><para>Specify a name for the activation file
descriptors. This is equivalent to setting
<varname>FileDescriptorName=</varname> in socket unit files, and
enables use of
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
@ -126,6 +136,7 @@
<varlistentry>
<term><varname>$LISTEN_FDS</varname></term>
<term><varname>$LISTEN_PID</varname></term>
<term><varname>$LISTEN_FDNAMES</varname></term>
<listitem><para>See
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem>
@ -165,6 +176,8 @@
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>cat</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -1,4 +1,4 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
@ -748,6 +748,22 @@
list.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>FileDescriptorName=</varname></term>
<listitem><para>Assigns a name to all file descriptors this
socket unit encapsulates. This is useful to help activated
services to identify specific file descriptors, if multiple
are passed. Services may use the
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>
call to acquire the names configured for the received file
descriptors. Names may contain any ASCII character, but must
exclude control characters or <literal>:</literal>, and must
be at most 255 characters in length. If this setting is not
used the file descriptor name defaults to the name of the
socket unit, including its <filename>.socket</filename>
suffix.</para></listitem>
</varlistentry>
</variablelist>
<para>Check
@ -768,9 +784,10 @@
<citerefentry><refentrytitle>systemd.kill</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.directives</refentrytitle><manvolnum>7</manvolnum></citerefentry>
<citerefentry><refentrytitle>systemd.directives</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>
</para>
<para>
For more extensive descriptions see the "systemd for Developers" series:
<ulink url="http://0pointer.de/blog/projects/socket-activation.html">Socket Activation</ulink>,

View File

@ -835,6 +835,7 @@
<varlistentry>
<term><varname>$LISTEN_PID</varname></term>
<term><varname>$LISTEN_FDS</varname></term>
<term><varname>$LISTEN_FDNAMES</varname></term>
<listitem><para>Set by systemd for supervised processes during
socket-based activation. See

View File

@ -26,7 +26,7 @@
#include <sys/wait.h>
#include <unistd.h>
#include "systemd/sd-daemon.h"
#include "sd-daemon.h"
#include "log.h"
#include "macro.h"
@ -38,6 +38,7 @@ static char** arg_listen = NULL;
static bool arg_accept = false;
static char** arg_args = NULL;
static char** arg_setenv = NULL;
static const char *arg_fdname = NULL;
static int add_epoll(int epoll_fd, int fd) {
struct epoll_event ev = {
@ -136,8 +137,8 @@ static int launch(char* name, char **argv, char **env, int fds) {
length = strv_length(arg_setenv);
/* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, NULL */
envp = new0(char *, length + 7);
/* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */
envp = new0(char *, length + 8);
if (!envp)
return log_oom();
@ -145,7 +146,9 @@ static int launch(char* name, char **argv, char **env, int fds) {
if (strchr(*s, '='))
envp[n_env++] = *s;
else {
_cleanup_free_ char *p = strappend(*s, "=");
_cleanup_free_ char *p;
p = strappend(*s, "=");
if (!p)
return log_oom();
envp[n_env] = strv_find_prefix(env, p);
@ -164,15 +167,37 @@ static int launch(char* name, char **argv, char **env, int fds) {
(asprintf((char**)(envp + n_env++), "LISTEN_PID=%d", getpid()) < 0))
return log_oom();
if (arg_fdname) {
char *e;
e = strappend("LISTEN_FDNAMES=", arg_fdname);
if (!e)
return log_oom();
for (i = 1; i < (unsigned) fds; i++) {
char *c;
c = strjoin(e, ":", arg_fdname, NULL);
if (!c) {
free(e);
return log_oom();
}
free(e);
e = c;
}
envp[n_env++] = e;
}
tmp = strv_join(argv, " ");
if (!tmp)
return log_oom();
log_info("Execing %s (%s)", name, tmp);
execvpe(name, argv, envp);
log_error_errno(errno, "Failed to execp %s (%s): %m", name, tmp);
return -errno;
return log_error_errno(errno, "Failed to execp %s (%s): %m", name, tmp);
}
static int launch1(const char* child, char** argv, char **env, int fd) {
@ -289,6 +314,7 @@ static void help(void) {
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_FDNAME,
};
static const struct option options[] = {
@ -297,11 +323,12 @@ static int parse_argv(int argc, char *argv[]) {
{ "listen", required_argument, NULL, 'l' },
{ "accept", no_argument, NULL, 'a' },
{ "setenv", required_argument, NULL, 'E' },
{ "environment", required_argument, NULL, 'E' }, /* alias */
{ "environment", required_argument, NULL, 'E' }, /* legacy alias */
{ "fdname", required_argument, NULL, ARG_FDNAME },
{}
};
int c;
int c, r;
assert(argc >= 0);
assert(argv);
@ -315,25 +342,27 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_VERSION:
return version();
case 'l': {
int r = strv_extend(&arg_listen, optarg);
case 'l':
r = strv_extend(&arg_listen, optarg);
if (r < 0)
return r;
return log_oom();
break;
}
case 'a':
arg_accept = true;
break;
case 'E': {
int r = strv_extend(&arg_setenv, optarg);
case 'E':
r = strv_extend(&arg_setenv, optarg);
if (r < 0)
return r;
return log_oom();
break;
}
case ARG_FDNAME:
arg_fdname = optarg;
break;
case '?':
return -EINVAL;

View File

@ -277,8 +277,8 @@ char **strv_split_newlines(const char *s) {
}
int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) {
size_t n = 0, allocated = 0;
_cleanup_strv_free_ char **l = NULL;
size_t n = 0, allocated = 0;
int r;
assert(t);
@ -302,13 +302,16 @@ int strv_split_extract(char ***t, const char *s, const char *separators, Extract
l[n] = NULL;
}
if (!l)
if (!l) {
l = new0(char*, 1);
if (!l)
return -ENOMEM;
}
*t = l;
l = NULL;
return 0;
return (int) n;
}
char *strv_join(char **l, const char *separator) {
@ -745,3 +748,41 @@ char **strv_skip(char **l, size_t n) {
return l;
}
int strv_extend_n(char ***l, const char *value, size_t n) {
size_t i, j, k;
char **nl;
assert(l);
if (!value)
return 0;
if (n == 0)
return 0;
/* Adds the value value n times to l */
k = strv_length(*l);
nl = realloc(*l, sizeof(char*) * (k + n + 1));
if (!nl)
return -ENOMEM;
*l = nl;
for (i = k; i < k + n; i++) {
nl[i] = strdup(value);
if (!nl[i])
goto rollback;
}
nl[i] = NULL;
return 0;
rollback:
for (j = k; j < i; i++)
free(nl[j]);
nl[k] = NULL;
return NULL;
}

View File

@ -158,3 +158,5 @@ static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, i
char ***strv_free_free(char ***l);
char **strv_skip(char **l, size_t n);
int strv_extend_n(char ***l, const char *value, size_t n);

View File

@ -6842,3 +6842,28 @@ int version(void) {
SYSTEMD_FEATURES);
return 0;
}
bool fdname_is_valid(const char *s) {
const char *p;
/* Validates a name for $LISTEN_NAMES. We basically allow
* everything ASCII that's not a control character. Also, as
* special exception the ":" character is not allowed, as we
* use that as field separator in $LISTEN_NAMES.
*
* Note that the empty string is explicitly allowed here.*/
if (!s)
return false;
for (p = s; *p; p++) {
if (*p < ' ')
return false;
if (*p >= 127)
return false;
if (*p == ':')
return false;
}
return p - s < 256;
}

View File

@ -941,3 +941,5 @@ int receive_one_fd(int transport_fd, int flags);
void nop_signal_handler(int sig);
int version(void);
bool fdname_is_valid(const char *s);

View File

@ -84,6 +84,25 @@ static int property_get_listen(
return sd_bus_message_close_container(reply);
}
static int property_get_fdname(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Socket *s = SOCKET(userdata);
assert(bus);
assert(reply);
assert(s);
return sd_bus_message_append(reply, "s", socket_fdname(s));
}
const sd_bus_vtable bus_socket_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("BindIPv6Only", "s", property_get_bind_ipv6_only, offsetof(Socket, bind_ipv6_only), SD_BUS_VTABLE_PROPERTY_CONST),
@ -128,6 +147,7 @@ const sd_bus_vtable bus_socket_vtable[] = {
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Socket, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("NConnections", "u", bus_property_get_unsigned, offsetof(Socket, n_connections), 0),
SD_BUS_PROPERTY("NAccepted", "u", bus_property_get_unsigned, offsetof(Socket, n_accepted), 0),
SD_BUS_PROPERTY("FileDescriptorName", "s", property_get_fdname, 0, 0),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),

View File

@ -21,18 +21,18 @@
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <glob.h>
#include <grp.h>
#include <poll.h>
#include <glob.h>
#include <utmpx.h>
#include <signal.h>
#include <string.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#include <utmpx.h>
#ifdef HAVE_PAM
#include <security/pam_appl.h>
@ -50,37 +50,38 @@
#include <sys/apparmor.h>
#endif
#include "barrier.h"
#include "sd-messages.h"
#include "rm-rf.h"
#include "strv.h"
#include "macro.h"
#include "capability.h"
#include "util.h"
#include "log.h"
#include "ioprio.h"
#include "securebits.h"
#include "namespace.h"
#include "exit-status.h"
#include "missing.h"
#include "utmp-wtmp.h"
#include "def.h"
#include "path-util.h"
#include "env-util.h"
#include "fileio.h"
#include "unit.h"
#include "async.h"
#include "selinux-util.h"
#include "errno-list.h"
#include "af-list.h"
#include "mkdir.h"
#include "smack-util.h"
#include "async.h"
#include "barrier.h"
#include "bus-endpoint.h"
#include "cap-list.h"
#include "capability.h"
#include "def.h"
#include "env-util.h"
#include "errno-list.h"
#include "exit-status.h"
#include "fileio.h"
#include "formats-util.h"
#include "ioprio.h"
#include "log.h"
#include "macro.h"
#include "missing.h"
#include "mkdir.h"
#include "namespace.h"
#include "path-util.h"
#include "process-util.h"
#include "terminal-util.h"
#include "rm-rf.h"
#include "securebits.h"
#include "selinux-util.h"
#include "signal-util.h"
#include "smack-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "unit.h"
#include "util.h"
#include "utmp-wtmp.h"
#ifdef HAVE_APPARMOR
#include "apparmor-util.h"
@ -1198,6 +1199,7 @@ static void do_idle_pipe_dance(int idle_pipe[4]) {
static int build_environment(
const ExecContext *c,
unsigned n_fds,
char ** fd_names,
usec_t watchdog_usec,
const char *home,
const char *username,
@ -1211,11 +1213,13 @@ static int build_environment(
assert(c);
assert(ret);
our_env = new0(char*, 10);
our_env = new0(char*, 11);
if (!our_env)
return -ENOMEM;
if (n_fds > 0) {
_cleanup_free_ char *joined = NULL;
if (asprintf(&x, "LISTEN_PID="PID_FMT, getpid()) < 0)
return -ENOMEM;
our_env[n_env++] = x;
@ -1223,6 +1227,15 @@ static int build_environment(
if (asprintf(&x, "LISTEN_FDS=%u", n_fds) < 0)
return -ENOMEM;
our_env[n_env++] = x;
joined = strv_join(fd_names, ":");
if (!joined)
return -ENOMEM;
x = strjoin("LISTEN_FDNAMES=", joined, NULL);
if (!x)
return -ENOMEM;
our_env[n_env++] = x;
}
if (watchdog_usec > 0) {
@ -1273,7 +1286,7 @@ static int build_environment(
}
our_env[n_env++] = NULL;
assert(n_env <= 10);
assert(n_env <= 11);
*ret = our_env;
our_env = NULL;
@ -1850,7 +1863,7 @@ static int exec_child(
#endif
}
r = build_environment(context, n_fds, params->watchdog_usec, home, username, shell, &our_env);
r = build_environment(context, n_fds, params->fd_names, params->watchdog_usec, home, username, shell, &our_env);
if (r < 0) {
*exit_status = EXIT_MEMORY;
return r;

View File

@ -208,19 +208,30 @@ struct ExecContext {
struct ExecParameters {
char **argv;
int *fds; unsigned n_fds;
int *fds;
char **fd_names;
unsigned n_fds;
char **environment;
bool apply_permissions;
bool apply_chroot;
bool apply_tty_stdin;
bool confirm_spawn;
bool selinux_context_net;
CGroupMask cgroup_supported;
const char *cgroup_path;
bool cgroup_delegate;
const char *runtime_prefix;
usec_t watchdog_usec;
int *idle_pipe;
char *bus_endpoint_path;
int bus_endpoint_fd;
};

View File

@ -287,6 +287,7 @@ Socket.MessageQueueMaxMessages, config_parse_long, 0,
Socket.MessageQueueMessageSize, config_parse_long, 0, offsetof(Socket, mq_msgsize)
Socket.RemoveOnStop, config_parse_bool, 0, offsetof(Socket, remove_on_stop)
Socket.Symlinks, config_parse_unit_path_strv_printf, 0, offsetof(Socket, symlinks)
Socket.FileDescriptorName, config_parse_fdname, 0, 0
Socket.Service, config_parse_socket_service, 0, 0
m4_ifdef(`HAVE_SMACK',
`Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack)

View File

@ -1475,10 +1475,10 @@ int config_parse_socket_service(
void *userdata) {
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
Socket *s = data;
int r;
Unit *x;
_cleanup_free_ char *p = NULL;
Socket *s = data;
Unit *x;
int r;
assert(filename);
assert(lvalue);
@ -1507,6 +1507,50 @@ int config_parse_socket_service(
return 0;
}
int config_parse_fdname(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ char *p = NULL;
Socket *s = data;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue)) {
s->fdname = mfree(s->fdname);
return 0;
}
r = unit_name_printf(UNIT(s), rvalue, &p);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue);
return 0;
}
if (!fdname_is_valid(p)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", p);
return 0;
}
free(s->fdname);
s->fdname = p;
p = NULL;
return 0;
}
int config_parse_service_sockets(
const char *unit,
const char *filename,

View File

@ -107,6 +107,7 @@ int config_parse_protect_system(const char* unit, const char *filename, unsigned
int config_parse_bus_name(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_utmp_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_working_directory(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_fdname(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);

View File

@ -495,6 +495,7 @@ static void manager_clean_environment(Manager *m) {
"MANAGERPID",
"LISTEN_PID",
"LISTEN_FDS",
"LISTEN_FDNAMES",
"WATCHDOG_PID",
"WATCHDOG_USEC",
NULL);

View File

@ -261,6 +261,7 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
sd_event_source_unref(fs->event_source);
}
free(fs->fdname);
safe_close(fs->fd);
free(fs);
}
@ -334,7 +335,7 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us
return 0;
}
static int service_add_fd_store(Service *s, int fd) {
static int service_add_fd_store(Service *s, int fd, const char *name) {
ServiceFDStore *fs;
int r;
@ -361,9 +362,13 @@ static int service_add_fd_store(Service *s, int fd) {
fs->fd = fd;
fs->service = s;
fs->fdname = strdup(name ?: "stored");
if (!fs->fdname)
return -ENOMEM;
r = sd_event_add_io(UNIT(s)->manager->event, &fs->event_source, fd, 0, on_fd_store_io, fs);
if (r < 0) {
free(fs->fdname);
free(fs);
return r;
}
@ -376,7 +381,7 @@ static int service_add_fd_store(Service *s, int fd) {
return 1;
}
static int service_add_fd_store_set(Service *s, FDSet *fds) {
static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) {
int r;
assert(s);
@ -391,7 +396,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds) {
if (fd < 0)
break;
r = service_add_fd_store(s, fd);
r = service_add_fd_store(s, fd, name);
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m");
if (r > 0) {
@ -957,56 +962,79 @@ static int service_coldplug(Unit *u) {
return 0;
}
static int service_collect_fds(Service *s, int **fds) {
static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
_cleanup_strv_free_ char **rfd_names = NULL;
_cleanup_free_ int *rfds = NULL;
int rn_fds = 0;
Iterator i;
Unit *u;
int rn_fds = 0, r;
assert(s);
assert(fds);
assert(fd_names);
if (s->socket_fd >= 0)
return -EINVAL;
if (s->socket_fd >= 0) {
SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) {
_cleanup_free_ int *cfds = NULL;
Socket *sock;
int cn_fds;
/* Pass the per-connection socket */
if (u->type != UNIT_SOCKET)
continue;
rfds = new(int, 1);
if (!rfds)
return -ENOMEM;
rfds[0] = s->socket_fd;
sock = SOCKET(u);
rfd_names = strv_new("connection", NULL);
if (!rfd_names)
return -ENOMEM;
cn_fds = socket_collect_fds(sock, &cfds);
if (cn_fds < 0)
return cn_fds;
rn_fds = 1;
} else {
Iterator i;
Unit *u;
if (cn_fds <= 0)
continue;
/* Pass all our configured sockets for singleton services */
if (!rfds) {
rfds = cfds;
rn_fds = cn_fds;
SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) {
_cleanup_free_ int *cfds = NULL;
Socket *sock;
int cn_fds;
cfds = NULL;
} else {
int *t;
if (u->type != UNIT_SOCKET)
continue;
t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int));
if (!t)
return -ENOMEM;
sock = SOCKET(u);
memcpy(t + rn_fds, cfds, cn_fds * sizeof(int));
cn_fds = socket_collect_fds(sock, &cfds);
if (cn_fds < 0)
return cn_fds;
rfds = t;
rn_fds += cn_fds;
if (cn_fds <= 0)
continue;
if (!rfds) {
rfds = cfds;
rn_fds = cn_fds;
cfds = NULL;
} else {
int *t;
t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int));
if (!t)
return -ENOMEM;
memcpy(t + rn_fds, cfds, cn_fds * sizeof(int));
rfds = t;
rn_fds += cn_fds;
}
r = strv_extend_n(&rfd_names, socket_fdname(sock), cn_fds);
if (r < 0)
return r;
}
}
if (s->n_fd_store > 0) {
ServiceFDStore *fs;
char **nl;
int *t;
t = realloc(rfds, (rn_fds + s->n_fd_store) * sizeof(int));
@ -1014,12 +1042,30 @@ static int service_collect_fds(Service *s, int **fds) {
return -ENOMEM;
rfds = t;
LIST_FOREACH(fd_store, fs, s->fd_store)
rfds[rn_fds++] = fs->fd;
nl = realloc(rfd_names, (rn_fds + s->n_fd_store + 1) * sizeof(char*));
if (!nl)
return -ENOMEM;
rfd_names = nl;
LIST_FOREACH(fd_store, fs, s->fd_store) {
rfds[rn_fds] = fs->fd;
rfd_names[rn_fds] = strdup(strempty(fs->fdname));
if (!rfd_names[rn_fds])
return -ENOMEM;
rn_fds++;
}
rfd_names[rn_fds] = NULL;
}
*fds = rfds;
*fd_names = rfd_names;
rfds = NULL;
rfd_names = NULL;
return rn_fds;
}
@ -1035,15 +1081,13 @@ static int service_spawn(
bool is_control,
pid_t *_pid) {
pid_t pid;
int r;
int *fds = NULL;
_cleanup_free_ int *fdsbuf = NULL;
unsigned n_fds = 0, n_env = 0;
_cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL;
_cleanup_free_ char *bus_endpoint_path = NULL;
_cleanup_strv_free_ char
**argv = NULL, **final_env = NULL, **our_env = NULL;
_cleanup_free_ int *fds = NULL;
unsigned n_fds = 0, n_env = 0;
const char *path;
pid_t pid;
ExecParameters exec_params = {
.apply_permissions = apply_permissions,
.apply_chroot = apply_chroot,
@ -1052,6 +1096,8 @@ static int service_spawn(
.selinux_context_net = s->socket_fd_selinux_context_net
};
int r;
assert(s);
assert(c);
assert(_pid);
@ -1071,17 +1117,11 @@ static int service_spawn(
s->exec_context.std_output == EXEC_OUTPUT_SOCKET ||
s->exec_context.std_error == EXEC_OUTPUT_SOCKET) {
if (s->socket_fd >= 0) {
fds = &s->socket_fd;
n_fds = 1;
} else {
r = service_collect_fds(s, &fdsbuf);
if (r < 0)
goto fail;
r = service_collect_fds(s, &fds, &fd_names);
if (r < 0)
goto fail;
fds = fdsbuf;
n_fds = r;
}
n_fds = r;
}
if (timeout > 0) {
@ -1119,7 +1159,7 @@ static int service_spawn(
goto fail;
}
if (UNIT_DEREF(s->accept_socket)) {
if (s->socket_fd >= 0) {
union sockaddr_union sa;
socklen_t salen = sizeof(sa);
@ -1185,6 +1225,7 @@ static int service_spawn(
exec_params.argv = argv;
exec_params.fds = fds;
exec_params.fd_names = fd_names;
exec_params.n_fds = n_fds;
exec_params.environment = final_env;
exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn;
@ -2047,13 +2088,16 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
}
LIST_FOREACH(fd_store, fs, s->fd_store) {
_cleanup_free_ char *c = NULL;
int copy;
copy = fdset_put_dup(fds, fs->fd);
if (copy < 0)
return copy;
unit_serialize_item_format(u, f, "fd-store-fd", "%i", copy);
c = cescape(fs->fdname);
unit_serialize_item_format(u, f, "fd-store-fd", "%i %s", copy, strempty(c));
}
if (s->main_exec_status.pid > 0) {
@ -2183,12 +2227,24 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
s->bus_endpoint_fd = fdset_remove(fds, fd);
}
} else if (streq(key, "fd-store-fd")) {
const char *fdv;
size_t pf;
int fd;
if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
pf = strcspn(value, WHITESPACE);
fdv = strndupa(value, pf);
if (safe_atoi(fdv, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse fd-store-fd value: %s", value);
else {
r = service_add_fd_store(s, fd);
_cleanup_free_ char *t = NULL;
const char *fdn;
fdn = value + pf;
fdn += strspn(fdn, WHITESPACE);
(void) cunescape(fdn, 0, &t);
r = service_add_fd_store(s, fd, t);
if (r < 0)
log_unit_error_errno(u, r, "Failed to add fd to store: %m");
else if (r > 0)
@ -2942,8 +2998,17 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
if (strv_find(tags, "WATCHDOG=1"))
service_reset_watchdog(s);
if (strv_find(tags, "FDSTORE=1"))
service_add_fd_store_set(s, fds);
if (strv_find(tags, "FDSTORE=1")) {
const char *name;
name = strv_find_startswith(tags, "FDNAME=");
if (name && !fdname_is_valid(name)) {
log_unit_warning(u, "Passed FDNAME= name is invalid, ignoring.");
name = NULL;
}
service_add_fd_store_set(s, fds, name);
}
/* Notify clients about changed status or main pid */
if (notify_dbus)

View File

@ -97,6 +97,7 @@ struct ServiceFDStore {
Service *service;
int fd;
char *fdname;
sd_event_source *event_source;
LIST_FIELDS(ServiceFDStore, fd_store);

View File

@ -508,6 +508,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) {
"%sTCPCongestion: %s\n"
"%sRemoveOnStop: %s\n"
"%sWritable: %s\n"
"%sFDName: %s\n"
"%sSELinuxContextFromNet: %s\n",
prefix, socket_state_to_string(s->state),
prefix, socket_result_to_string(s->result),
@ -525,6 +526,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) {
prefix, strna(s->tcp_congestion),
prefix, yes_no(s->remove_on_stop),
prefix, yes_no(s->writable),
prefix, socket_fdname(s),
prefix, yes_no(s->selinux_context_from_net));
if (s->control_pid > 0)
@ -2760,6 +2762,19 @@ static int socket_get_timeout(Unit *u, uint64_t *timeout) {
return 1;
}
char *socket_fdname(Socket *s) {
assert(s);
/* Returns the name to use for $LISTEN_NAMES. If the user
* didn't specify anything specifically, use the socket unit's
* name as fallback. */
if (s->fdname)
return s->fdname;
return UNIT(s)->id;
}
static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = {
[SOCKET_EXEC_START_PRE] = "StartPre",
[SOCKET_EXEC_START_CHOWN] = "StartChown",

View File

@ -154,6 +154,8 @@ struct Socket {
char *user, *group;
bool reset_cpu_usage:1;
char *fdname;
};
/* Called from the service code when collecting fds */
@ -164,6 +166,10 @@ void socket_connection_unref(Socket *s);
void socket_free_ports(Socket *s);
int socket_instantiate_service(Socket *s);
char *socket_fdname(Socket *s);
extern const UnitVTable socket_vtable;
const char* socket_exec_command_to_string(SocketExecCommand i) _const_;
@ -173,5 +179,3 @@ const char* socket_result_to_string(SocketResult i) _const_;
SocketResult socket_result_from_string(const char *s) _pure_;
const char* socket_port_type_to_string(SocketPort *p) _pure_;
int socket_instantiate_service(Socket *s);

View File

@ -19,25 +19,37 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stddef.h>
#include <limits.h>
#include <mqueue.h>
#include <netinet/in.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#include "util.h"
#include "path-util.h"
#include "socket-util.h"
#include "strv.h"
#include "util.h"
#include "sd-daemon.h"
static void unsetenv_all(bool unset_environment) {
if (!unset_environment)
return;
unsetenv("LISTEN_PID");
unsetenv("LISTEN_FDS");
unsetenv("LISTEN_FDNAMES");
}
_public_ int sd_listen_fds(int unset_environment) {
const char *e;
unsigned n;
@ -79,12 +91,49 @@ _public_ int sd_listen_fds(int unset_environment) {
r = (int) n;
finish:
if (unset_environment) {
unsetenv("LISTEN_PID");
unsetenv("LISTEN_FDS");
unsetenv_all(unset_environment);
return r;
}
_public_ int sd_listen_fds_with_names(int unset_environment, char ***names) {
_cleanup_strv_free_ char **l = NULL;
bool have_names;
int n_names = 0, n_fds;
const char *e;
int r;
if (!names)
return sd_listen_fds(unset_environment);
e = getenv("LISTEN_FDNAMES");
if (e) {
n_names = strv_split_extract(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (n_names < 0) {
unsetenv_all(unset_environment);
return n_names;
}
have_names = true;
} else
have_names = false;
n_fds = sd_listen_fds(unset_environment);
if (n_fds <= 0)
return n_fds;
if (have_names) {
if (n_names != n_fds)
return -EINVAL;
} else {
r = strv_extend_n(&l, "unknown", n_fds);
if (r < 0)
return r;
}
return r;
*names = l;
l = NULL;
return n_fds;
}
_public_ int sd_is_fifo(int fd, const char *path) {

View File

@ -76,6 +76,8 @@ _SD_BEGIN_DECLARATIONS;
*/
int sd_listen_fds(int unset_environment);
int sd_listen_fds_with_names(int unset_environment, char ***names);
/*
Helper call for identifying a passed file descriptor. Returns 1 if
the file descriptor is a FIFO in the file system stored under the

View File

@ -21,9 +21,22 @@
#include <unistd.h>
#include "systemd/sd-daemon.h"
#include "sd-daemon.h"
#include "strv.h"
int main(int argc, char*argv[]) {
_cleanup_strv_free_ char **l = NULL;
int n, i;
n = sd_listen_fds_with_names(false, &l);
if (n < 0) {
log_error_errno(n, "Failed to get listening fds: %m");
return EXIT_FAILURE;
}
for (i = 0; i < n; i++)
log_info("fd=%i name=%s\n", SD_LISTEN_FDS_START + i, l[i]);
sd_notify(0,
"STATUS=Starting up");
@ -49,5 +62,5 @@ int main(int argc, char*argv[]) {
"STOPPING=1");
sleep(5);
return 0;
return EXIT_SUCCESS;
}

View File

@ -155,7 +155,7 @@ static void test_strv_join(void) {
static void test_strv_quote_unquote(const char* const *split, const char *quoted) {
_cleanup_free_ char *p;
_cleanup_strv_free_ char **s;
_cleanup_strv_free_ char **s = NULL;
char **t;
int r;
@ -166,7 +166,7 @@ static void test_strv_quote_unquote(const char* const *split, const char *quoted
assert_se(streq(p, quoted));
r = strv_split_extract(&s, quoted, WHITESPACE, EXTRACT_QUOTES);
assert_se(r == 0);
assert_se(r == (int) strv_length(s));
assert_se(s);
STRV_FOREACH(t, s) {
assert_se(*t);
@ -183,7 +183,7 @@ static void test_strv_unquote(const char *quoted, char **list) {
int r;
r = strv_split_extract(&s, quoted, WHITESPACE, EXTRACT_QUOTES);
assert_se(r == 0);
assert_se(r == (int) strv_length(list));
assert_se(s);
j = strv_join(s, " | ");
assert_se(j);
@ -225,7 +225,7 @@ static void test_strv_split_extract(void) {
int r;
r = strv_split_extract(&l, str, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
assert_se(r == 0);
assert_se(r == (int) strv_length(l));
assert_se(streq_ptr(l[0], ""));
assert_se(streq_ptr(l[1], "foo:bar"));
assert_se(streq_ptr(l[2], ""));
@ -591,6 +591,33 @@ static void test_strv_skip(void) {
test_strv_skip_one(STRV_MAKE(NULL), 55, STRV_MAKE(NULL));
}
static void test_strv_extend_n(void) {
_cleanup_strv_free_ char **v = NULL;
v = strv_new("foo", "bar", NULL);
assert_se(v);
assert_se(strv_extend_n(&v, "waldo", 3) >= 0);
assert_se(strv_extend_n(&v, "piep", 2) >= 0);
assert_se(streq(v[0], "foo"));
assert_se(streq(v[1], "bar"));
assert_se(streq(v[2], "waldo"));
assert_se(streq(v[3], "waldo"));
assert_se(streq(v[4], "waldo"));
assert_se(streq(v[5], "piep"));
assert_se(streq(v[6], "piep"));
assert_se(v[7] == NULL);
v = strv_free(v);
assert_se(strv_extend_n(&v, "foo", 1) >= 0);
assert_se(strv_extend_n(&v, "bar", 0) >= 0);
assert_se(streq(v[0], "foo"));
assert_se(v[1] == NULL);
}
int main(int argc, char *argv[]) {
test_specifier_printf();
test_strv_foreach();
@ -650,6 +677,7 @@ int main(int argc, char *argv[]) {
test_strv_reverse();
test_strv_shell_escape();
test_strv_skip();
test_strv_extend_n();
return 0;
}