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 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel