i3/libi3/create_socket.c

82 lines
2.0 KiB
C
Raw Normal View History

/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h"
#include <unistd.h>
#include <libgen.h>
#include <err.h>
2021-11-04 19:16:41 +01:00
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
/*
* Creates the UNIX domain socket at the given path, sets it to non-blocking
* mode, bind()s and listen()s on it.
*
* The full path to the socket is stored in the char* that out_socketpath points
* to.
*
*/
int create_socket(const char *filename, char **out_socketpath) {
char *resolved = resolve_tilde(filename);
DLOG("Creating UNIX socket at %s\n", resolved);
char *copy = sstrdup(resolved);
const char *dir = dirname(copy);
if (!path_exists(dir)) {
mkdirp(dir, DEFAULT_DIR_MODE);
}
free(copy);
Do not replace existing IPC socket (#4638) Imagine you are a happy i3 user and want to also write patches for i3. You use "Xephyr :1" to get another X11 server and then start your newly build i3 in it with "DISPLAY=:1 ./i3". You test your changes and everything seems fine. You are happy. Later that day, you try to log out, but the $mod+Shift+e key binding from the default config no longer works. i3-msg cannot connect to the IPC socket because "No such file or directory". What is going on? The problem boils down to $I3SOCK having something like two meanings. When i3 starts, it sets the environment variable $I3SOCK to the path of its IPC socket. That way, any process started from i3 inherits this and i3-msg knows how to talk to i3. However, when this variable is already set when i3 starts, then i3 will replace the existing socket. Thus, in the earlier experiments, the "separate i3" that was used for experimenting stole the "main i3"'s socket and replaced it with its own. When it exited, it deleted that socket. This commit adds half a work around to this problem: When creating the IPC socket, i3 will now first try to connect() to the socket. If this succeeds, it will complain and refuse to use this socket. If the connect() call fails, it will proceed as usual and create the socket. Note that trying to connect() to a socket that no process listens on will fail. Thus, this new code only really "triggers" when some process is actively listening on this socket and accepting connections. Example output for when the socket is already in use: $ I3SOCK=/tmp/sdfdsf DISPLAY=:2 ./i3 31.10.2021 17:03:55 - [libi3] ERROR: Refusing to create UNIX socket at /tmp/sdfdsf: Socket is already in use 31.10.2021 17:03:55 - ERROR: Could not create the IPC socket, IPC disabled This commit sadly only provides part of the solution. i3 will still delete the socket when shutting down, even if it failed to create the IPC socket. Thus, the ipc socket will still break, but now only later. This will be fixed separately. First-step-towards-fixing: https://github.com/i3/i3/issues/4381 Signed-off-by: Uli Schlachter <psychon@znc.in>
2021-11-04 17:22:22 +01:00
/* Check if the socket is in use by another process (this call does not
* succeed if the socket is stale / the owner already exited) */
int sockfd = ipc_connect_impl(resolved);
if (sockfd != -1) {
ELOG("Refusing to create UNIX socket at %s: Socket is already in use\n", resolved);
close(sockfd);
errno = EEXIST;
return -1;
}
/* Unlink the unix domain socket before */
unlink(resolved);
Do not replace existing IPC socket (#4638) Imagine you are a happy i3 user and want to also write patches for i3. You use "Xephyr :1" to get another X11 server and then start your newly build i3 in it with "DISPLAY=:1 ./i3". You test your changes and everything seems fine. You are happy. Later that day, you try to log out, but the $mod+Shift+e key binding from the default config no longer works. i3-msg cannot connect to the IPC socket because "No such file or directory". What is going on? The problem boils down to $I3SOCK having something like two meanings. When i3 starts, it sets the environment variable $I3SOCK to the path of its IPC socket. That way, any process started from i3 inherits this and i3-msg knows how to talk to i3. However, when this variable is already set when i3 starts, then i3 will replace the existing socket. Thus, in the earlier experiments, the "separate i3" that was used for experimenting stole the "main i3"'s socket and replaced it with its own. When it exited, it deleted that socket. This commit adds half a work around to this problem: When creating the IPC socket, i3 will now first try to connect() to the socket. If this succeeds, it will complain and refuse to use this socket. If the connect() call fails, it will proceed as usual and create the socket. Note that trying to connect() to a socket that no process listens on will fail. Thus, this new code only really "triggers" when some process is actively listening on this socket and accepting connections. Example output for when the socket is already in use: $ I3SOCK=/tmp/sdfdsf DISPLAY=:2 ./i3 31.10.2021 17:03:55 - [libi3] ERROR: Refusing to create UNIX socket at /tmp/sdfdsf: Socket is already in use 31.10.2021 17:03:55 - ERROR: Could not create the IPC socket, IPC disabled This commit sadly only provides part of the solution. i3 will still delete the socket when shutting down, even if it failed to create the IPC socket. Thus, the ipc socket will still break, but now only later. This will be fixed separately. First-step-towards-fixing: https://github.com/i3/i3/issues/4381 Signed-off-by: Uli Schlachter <psychon@znc.in>
2021-11-04 17:22:22 +01:00
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket()");
free(resolved);
return -1;
}
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
perror("bind()");
free(resolved);
return -1;
}
set_nonblock(sockfd);
if (listen(sockfd, 5) < 0) {
perror("listen()");
free(resolved);
return -1;
}
free(*out_socketpath);
*out_socketpath = resolved;
return sockfd;
}