[systemd-devel] [PATCH 2/3] sd-daemon: Use LISTEN_NAMES env when available
Krzysztof Opasiak
k.opasiak at samsung.com
Fri May 15 08:09:11 PDT 2015
LISTEN_NAMES environment variable contains details
about received file descriptors. Let's try to use it
instead of doing always two stats.
This commit reworks all sd_is_*() functions to try
parse LISTEN_NAMES variable in first step and do
stats only as fallback procedure or if field for given
fd is empty.
We do this only for fds from 3 up to 3 + LISTEN_FDS - 1.
For all other fds or when LISTEN_NAMES is not available
we still do two stats.
Please be careful, this may cause some troubles.
when service has not unset environment and close fdx where
3 <= x < 3 + LISTEN_FDS and open something else (file,
socket etc) he will probably get the same x as fd.
If he call sd_is_*() function with this fd we will try
to use content of LISTEN_NAMES but in is not valid now.
A solution is to not use sd-daemon library in such
scenario or simply unset environment before calling
sd-daemon functions for fds other than received from sd.
---
src/libsystemd/sd-daemon/sd-daemon.c | 463 +++++++++++++++++++++++++++++++---
1 file changed, 430 insertions(+), 33 deletions(-)
diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c
index 82ac72c..f9d7a74 100644
--- a/src/libsystemd/sd-daemon/sd-daemon.c
+++ b/src/libsystemd/sd-daemon/sd-daemon.c
@@ -38,6 +38,8 @@
#include "socket-util.h"
#include "sd-daemon.h"
+#define SIGNUM(x) (((x) > 0) - ((x) < 0))
+
_public_ int sd_listen_fds(int unset_environment) {
const char *e;
unsigned n;
@@ -80,6 +82,7 @@ _public_ int sd_listen_fds(int unset_environment) {
finish:
if (unset_environment) {
+ unsetenv("LISTEN_NAMES");
unsetenv("LISTEN_PID");
unsetenv("LISTEN_FDS");
}
@@ -87,10 +90,89 @@ finish:
return r;
}
-_public_ int sd_is_fifo(int fd, const char *path) {
- struct stat st_fd;
+static const char *sd_get_fd_name(int fd) {
+ static const char sep = ':';
+ static const char escape = '\\';
+ const char *env = NULL;
+ const char *e = NULL;
+ int i;
- assert_return(fd >= 0, -EINVAL);
+ assert_return(fd >= 3, NULL);
+
+ env = getenv("LISTEN_NAMES");
+ if (!env)
+ goto out;
+
+ for (i = 3, e = env; *e && i < fd; ++e)
+ if (*e == sep && (e == env || *(e - 1) != escape))
+ ++i;
+
+ if (e && !*e)
+ e = NULL;
+ out:
+ return e;
+}
+
+static int sd_env_mem_cmp(const char *env_path, const char *buf, int length) {
+ static const char sep = ':';
+ static const char escape = '\\';
+ int i = 0;
+
+ /* FIXME: We assume we were able to print the memory to
+ * env. That is actually an invalid assumption. */
+ while (*env_path && i < length) {
+ if (*env_path == sep)
+ return *buf ? -1 : 0;
+
+ /* skip \ as escpae character */
+ if (*env_path == escape && *(env_path + 1) == sep)
+ ++env_path;
+
+ if (*env_path != *buf)
+ break;
+
+ ++env_path;
+ ++buf;
+ ++i;
+ }
+
+ return *env_path == sep && i == length ? 0 : SIGNUM(*env_path - *buf);
+}
+
+static int sd_env_path_cmp(const char *env_path, const char *path) {
+ static const char sep = ':';
+ static const char escape = '\\';
+
+ while (*env_path && *path) {
+ if (*env_path == sep)
+ return *path ? -1 : 0;
+
+ /* skip \ as escpae character */
+ if (*env_path == escape && *(env_path + 1) == sep)
+ ++env_path;
+
+ if (*env_path != *path)
+ break;
+
+ /* Because paths:
+ * /a/b/c////d
+ * /a//b////////c/d
+ * points to the same place
+ * we have to skip all redundant slashes */
+ do
+ ++env_path;
+ while (*env_path == '/' && *(env_path + 1) == '/');
+
+ do
+ ++path;
+ while (*path == '/' && *(path + 1) == '/');
+ }
+
+ return *env_path == sep && !*path ? 0 : SIGNUM(*env_path - *path);
+}
+
+static int sd_is_fifo_stat(int fd, const char *path) {
+ struct stat st_fd;
if (fstat(fd, &st_fd) < 0)
return -errno;
@@ -117,11 +199,40 @@ _public_ int sd_is_fifo(int fd, const char *path) {
return 1;
}
-_public_ int sd_is_special(int fd, const char *path) {
- struct stat st_fd;
+_public_ int sd_is_fifo(int fd, const char *path) {
+ static const char sep = ':';
+ static const char fifo_token[] = "fifo";
+ const char *fd_name;
assert_return(fd >= 0, -EINVAL);
+ fd_name = sd_get_fd_name(fd);
+ if (!fd_name || !*fd_name || *fd_name == sep)
+ goto do_stats;
+
+ if (strncmp(fifo_token, fd_name, sizeof(fifo_token) - 1))
+ return 0;
+
+ if (path) {
+ fd_name += sizeof(fifo_token) - 1;
+ /* If env has wrong format we don't care about it and
+ * use fallback procedure with stats. Also if given path
+ * is not absolute we have to do stats instead of simply
+ * strings compare */
+ if (*fd_name != '=' || !path_is_absolute(path))
+ goto do_stats;
+
+ return !sd_env_path_cmp(fd_name + 1, path);
+ }
+
+ return 1;
+ do_stats:
+ return sd_is_fifo_stat(fd, path);
+}
+
+static int sd_is_special_stat(int fd, const char *path) {
+ struct stat st_fd;
+
if (fstat(fd, &st_fd) < 0)
return -errno;
@@ -152,8 +263,72 @@ _public_ int sd_is_special(int fd, const char *path) {
return 1;
}
-static int sd_is_socket_internal(int fd, int type, int listening) {
+_public_ int sd_is_special(int fd, const char *path) {
+ static const char sep = ':';
+ static const char special_token[] = "special";
+ const char *fd_name;
+
+ assert_return(fd >= 0, -EINVAL);
+
+ fd_name = sd_get_fd_name(fd);
+ if (!fd_name || !*fd_name || *fd_name == sep)
+ goto do_stats;
+
+ if (strncmp(special_token, fd_name, sizeof(special_token) - 1))
+ return 0;
+
+ if (path) {
+ fd_name += sizeof(special_token) - 1;
+ /* If env has wrong format we don't care about it and
+ * use fallback procedure with stats. Also if given path
+ * is not absolute we have to do stats instead of simply
+ * strings compare */
+ if (*fd_name != '=' || !path_is_absolute(path))
+ goto do_stats;
+
+ return !sd_env_path_cmp(fd_name + 1, path);
+ }
+
+ return 1;
+ do_stats:
+ return sd_is_special_stat(fd, path);
+}
+
+static int sd_is_socket_type(int fd, int type) {
+ int other_type = 0;
+ socklen_t l = sizeof(other_type);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(other_type))
+ return -EINVAL;
+
+ if (other_type != type)
+ return 0;
+
+ return 1;
+}
+
+static int sd_is_socket_listening(int fd, int listening) {
+ int accepting = 0;
+ socklen_t l = sizeof(accepting);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(accepting))
+ return -EINVAL;
+
+ if (!accepting != !listening)
+ return 0;
+
+ return 1;
+}
+
+static int sd_is_socket_internal_stat(int fd, int type, int listening) {
struct stat st_fd;
+ int ret;
assert_return(fd >= 0, -EINVAL);
assert_return(type >= 0, -EINVAL);
@@ -165,43 +340,80 @@ static int sd_is_socket_internal(int fd, int type, int listening) {
return 0;
if (type != 0) {
- int other_type = 0;
- socklen_t l = sizeof(other_type);
+ ret = sd_is_socket_type(fd, type);
+ if (ret <= 0)
+ return ret;
+ }
- if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
- return -errno;
+ if (listening >= 0) {
+ ret = sd_is_socket_listening(fd, listening);
+ if (ret <= 0)
+ return ret;
+ }
- if (l != sizeof(other_type))
- return -EINVAL;
+ return 1;
+}
- if (other_type != type)
- return 0;
+static int sd_is_socket_internal(int fd, const char *fd_name,
+ int type, int listening) {
+ static const char socket_token[] = "socket";
+ static const char netlink_token[] = "netlink";
+ int ret;
+
+ if (strncmp(socket_token, fd_name, sizeof(socket_token) - 1) &&
+ strncmp(netlink_token, fd_name, sizeof(netlink_token) - 1))
+ return 0;
+
+ if (type != 0) {
+ ret = sd_is_socket_type(fd, type);
+ if (ret <= 0)
+ return ret;
}
if (listening >= 0) {
- int accepting = 0;
- socklen_t l = sizeof(accepting);
+ ret = sd_is_socket_listening(fd, listening);
+ if (ret <= 0)
+ return ret;
+ }
- if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
- return -errno;
+ return 1;
+}
- if (l != sizeof(accepting))
+static int sd_get_socket_family(int fd, const char *fd_name, int *family) {
+ static const char sep = ':';
+ static const char socket_token[] = "socket";
+ static const char netlink_token[] = "netlink";
+ int ret = 0;
+
+ if (!strncmp(netlink_token, fd_name, sizeof(netlink_token) - 1)) {
+ *family = AF_NETLINK;
+ } else if (!strncmp(socket_token, fd_name, sizeof(socket_token) - 1)) {
+ fd_name += sizeof(socket_token) - 1;
+ if (*fd_name != '=')
return -EINVAL;
- if (!accepting != !listening)
- return 0;
+ ++fd_name;
+ if (*fd_name == '[')
+ *family = AF_INET6;
+ else if (*fd_name == '/' || *fd_name == '@' || *fd_name == '<')
+ *family = AF_UNIX;
+ else if (*fd_name != sep && *fd_name)
+ *family = AF_INET;
+ else
+ return -EINVAL;
+
+ ret = 0;
+ } else {
+ ret = -EINVAL;
}
- return 1;
+ return ret;
}
-_public_ int sd_is_socket(int fd, int family, int type, int listening) {
+static int sd_is_socket_stat(int fd, int family, int type, int listening) {
int r;
- assert_return(fd >= 0, -EINVAL);
- assert_return(family >= 0, -EINVAL);
-
- r = sd_is_socket_internal(fd, type, listening);
+ r = sd_is_socket_internal_stat(fd, type, listening);
if (r <= 0)
return r;
@@ -221,7 +433,36 @@ _public_ int sd_is_socket(int fd, int family, int type, int listening) {
return 1;
}
-_public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+_public_ int sd_is_socket(int fd, int family, int type, int listening) {
+ static const char sep = ':';
+ const char *fd_name;
+ int ret;
+
+ assert_return(fd >= 0, -EINVAL);
+ assert_return(type >= 0, -EINVAL);
+
+ fd_name = sd_get_fd_name(fd);
+ if (!fd_name || !*fd_name || *fd_name == sep)
+ goto do_stats;
+
+ ret = sd_is_socket_internal(fd, fd_name, type, listening);
+ if (ret <= 0)
+ return ret;
+
+ if (family > 0) {
+ int other_family;
+
+ ret = sd_get_socket_family(fd, fd_name, &other_family);
+
+ return ret ?: other_family == family;
+ }
+
+ return 1;
+ do_stats:
+ return sd_is_socket_stat(fd, family, type, listening);
+}
+
+static int sd_is_socket_inet_stat(int fd, int family, int type, int listening, uint16_t port) {
union sockaddr_union sockaddr = {};
socklen_t l = sizeof(sockaddr);
int r;
@@ -229,7 +470,7 @@ _public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint
assert_return(fd >= 0, -EINVAL);
assert_return(IN_SET(family, 0, AF_INET, AF_INET6), -EINVAL);
- r = sd_is_socket_internal(fd, type, listening);
+ r = sd_is_socket_internal_stat(fd, type, listening);
if (r <= 0)
return r;
@@ -264,14 +505,89 @@ _public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint
return 1;
}
-_public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+_public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+ static const char sep = ':';
+ static const char escape = '\\';
+ static const char socket_token[] = "socket";
+ const char *fd_name;
+ int other_family;
+ int ret;
+
+ assert_return(fd >= 0, -EINVAL);
+ assert_return(IN_SET(family, 0, AF_INET, AF_INET6), -EINVAL);
+
+ fd_name = sd_get_fd_name(fd);
+ if (!fd_name || !*fd_name || *fd_name == sep)
+ goto do_stats;
+
+ ret = sd_is_socket_internal(fd, fd_name, type, listening);
+ if (ret <= 0)
+ return ret;
+
+ ret = sd_get_socket_family(fd, fd_name, &other_family);
+ /* if we were unable do get family just try to do stats */
+ if (ret < 0)
+ goto do_stats;
+
+ if (!IN_SET(family, AF_INET, AF_INET6))
+ return 0;
+
+ if (family > 0 && family != other_family)
+ return 0;
+
+ if (port > 0) {
+ const char *p;
+ const char *s;
+ char *endptr;
+ const char *val = fd_name;
+ unsigned long port_buf;
+
+ fd_name += sizeof(socket_token);
+ if (other_family == AF_INET6) {
+ for (p = fd_name; *p && *p != ']' &&
+ !(*p == sep && *(p - 1) != escape);
+ ++p);
+ val = p;
+ } else {
+ val = fd_name;
+ }
+
+ s = strchr(val, ':');
+ p = strstr(val, "\\:");
+
+ /* Check if we have an escaped : in current field.
+ * if no let's rewind to start and check if we don't
+ * have only port number without ip address */
+ if (!s || !p || p != s - 1) {
+ p = fd_name;
+ } else {
+ p += 2;
+ }
+
+ port_buf = strtoul(p, &endptr, 0);
+ /* if we were unable to parse our port let's try to do stats.
+ * This should not happen very often. */
+ if (!endptr || endptr == p ||
+ (*endptr != '\0' && *endptr != sep) ||
+ errno || port_buf > 65535)
+ goto do_stats;
+
+ return port_buf == port;
+ }
+
+ return 1;
+ do_stats:
+ return sd_is_socket_inet_stat(fd, family, type, listening, port);
+}
+
+static int sd_is_socket_unix_stat(int fd, int type, int listening, const char *path, size_t length) {
union sockaddr_union sockaddr = {};
socklen_t l = sizeof(sockaddr);
int r;
assert_return(fd >= 0, -EINVAL);
- r = sd_is_socket_internal(fd, type, listening);
+ r = sd_is_socket_internal_stat(fd, type, listening);
if (r <= 0)
return r;
@@ -307,11 +623,61 @@ _public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path
return 1;
}
-_public_ int sd_is_mq(int fd, const char *path) {
- struct mq_attr attr;
+_public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+ static const char sep = ':';
+ static const char socket_token[] = "socket";
+ static const char unnamed_token[] = "<unnamed>";
+ const char *fd_name;
+ int family;
+ int ret;
assert_return(fd >= 0, -EINVAL);
+ fd_name = sd_get_fd_name(fd);
+ if (!fd_name || !*fd_name || *fd_name == sep)
+ goto do_stats;
+
+ ret = sd_is_socket_internal(fd, fd_name, type, listening);
+ if (ret <= 0)
+ return ret;
+
+ ret = sd_get_socket_family(fd, fd_name, &family);
+ /* if we were unable do get family just try to do stats */
+ if (ret < 0)
+ goto do_stats;
+
+ if (family != AF_UNIX)
+ return 0;
+
+ if (path) {
+ fd_name += sizeof(socket_token);
+
+ if (length == 0)
+ length = strlen(path);
+
+ if (length == 0)
+ /* Unnamed socket */
+ return !strncmp(fd_name, unnamed_token,
+ sizeof(unnamed_token) - 1);
+
+ if (path[0])
+ /* Normal path socket */
+ return !sd_env_path_cmp(fd_name, path);
+ else
+ /* Abstract namespace socket */
+ return *fd_name == '@' &&
+ !sd_env_mem_cmp(fd_name + 1, path + 1,
+ length - 1);
+ }
+
+ return 1;
+ do_stats:
+ return sd_is_socket_unix_stat(fd, type, listening, path, length);
+}
+
+static int sd_is_mq_stat(int fd, const char *path) {
+ struct mq_attr attr;
+
if (mq_getattr(fd, &attr) < 0)
return -errno;
@@ -338,6 +704,37 @@ _public_ int sd_is_mq(int fd, const char *path) {
return 1;
}
+_public_ int sd_is_mq(int fd, const char *path) {
+ static const char sep = ':';
+ static const char mqueue_token[] = "mqueue";
+ const char *fd_name;
+
+ assert_return(fd >= 0, -EINVAL);
+
+ fd_name = sd_get_fd_name(fd);
+ if (!fd_name || !*fd_name || *fd_name == sep)
+ goto do_stats;
+
+ if (strncmp(mqueue_token, fd_name, sizeof(mqueue_token) - 1))
+ return 0;
+
+ if (path) {
+ assert_return(path_is_absolute(path), -EINVAL);
+
+ fd_name += sizeof(mqueue_token) - 1;
+ /* If env has wrong format we don't care about it and
+ * use fallback procedure with stats. */
+ if (*fd_name != '=')
+ goto do_stats;
+
+ return !sd_env_path_cmp(fd_name + 1, path);
+ }
+
+ return 1;
+ do_stats:
+ return sd_is_mq_stat(fd, path);
+}
+
_public_ int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds) {
union sockaddr_union sockaddr = {
.sa.sa_family = AF_UNIX,
--
1.7.9.5
More information about the systemd-devel
mailing list