[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