Skip to content

Commit 9fd1902

Browse files
jeffhostetlergitster
authored andcommitted
unix-stream-server: create unix domain socket under lock
Create a wrapper class for `unix_stream_listen()` that uses a ".lock" lockfile to create the unix domain socket in a race-free manner. Unix domain sockets have a fundamental problem on Unix systems because they persist in the filesystem until they are deleted. This is independent of whether a server is actually listening for connections. Well-behaved servers are expected to delete the socket when they shutdown. A new server cannot easily tell if a found socket is attached to an active server or is leftover cruft from a dead server. The traditional solution used by `unix_stream_listen()` is to force delete the socket pathname and then create a new socket. This solves the latter (cruft) problem, but in the case of the former, it orphans the existing server (by stealing the pathname associated with the socket it is listening on). We cannot directly use a .lock lockfile to create the socket because the socket is created by `bind(2)` rather than the `open(2)` mechanism used by `tempfile.c`. As an alternative, we hold a plain lockfile ("<path>.lock") as a mutual exclusion device. Under the lock, we test if an existing socket ("<path>") is has an active server. If not, we create a new socket and begin listening. Then we use "rollback" to delete the lockfile in all cases. This wrapper code conceptually exists at a higher-level than the core unix_stream_connect() and unix_stream_listen() routines that it consumes. It is isolated in a wrapper class for clarity. Signed-off-by: Jeff Hostetler <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 77e522c commit 9fd1902

File tree

4 files changed

+160
-1
lines changed

4 files changed

+160
-1
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,7 @@ ifdef NO_UNIX_SOCKETS
16771677
BASIC_CFLAGS += -DNO_UNIX_SOCKETS
16781678
else
16791679
LIB_OBJS += unix-socket.o
1680+
LIB_OBJS += unix-stream-server.o
16801681
endif
16811682

16821683
ifdef USE_WIN32_IPC

contrib/buildsystems/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
243243

244244
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
245245
add_compile_definitions(PROCFS_EXECUTABLE_PATH="/proc/self/exe" HAVE_DEV_TTY )
246-
list(APPEND compat_SOURCES unix-socket.c)
246+
list(APPEND compat_SOURCES unix-socket.c unix-stream-server.c)
247247
endif()
248248

249249
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")

unix-stream-server.c

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#include "cache.h"
2+
#include "lockfile.h"
3+
#include "unix-socket.h"
4+
#include "unix-stream-server.h"
5+
6+
#define DEFAULT_LOCK_TIMEOUT (100)
7+
8+
/*
9+
* Try to connect to a unix domain socket at `path` (if it exists) and
10+
* see if there is a server listening.
11+
*
12+
* We don't know if the socket exists, whether a server died and
13+
* failed to cleanup, or whether we have a live server listening, so
14+
* we "poke" it.
15+
*
16+
* We immediately hangup without sending/receiving any data because we
17+
* don't know anything about the protocol spoken and don't want to
18+
* block while writing/reading data. It is sufficient to just know
19+
* that someone is listening.
20+
*/
21+
static int is_another_server_alive(const char *path,
22+
const struct unix_stream_listen_opts *opts)
23+
{
24+
int fd = unix_stream_connect(path, opts->disallow_chdir);
25+
if (fd >= 0) {
26+
close(fd);
27+
return 1;
28+
}
29+
30+
return 0;
31+
}
32+
33+
int unix_ss_create(const char *path,
34+
const struct unix_stream_listen_opts *opts,
35+
long timeout_ms,
36+
struct unix_ss_socket **new_server_socket)
37+
{
38+
struct lock_file lock = LOCK_INIT;
39+
int fd_socket;
40+
struct unix_ss_socket *server_socket;
41+
42+
*new_server_socket = NULL;
43+
44+
if (timeout_ms < 0)
45+
timeout_ms = DEFAULT_LOCK_TIMEOUT;
46+
47+
/*
48+
* Create a lock at "<path>.lock" if we can.
49+
*/
50+
if (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0)
51+
return -1;
52+
53+
/*
54+
* If another server is listening on "<path>" give up. We do not
55+
* want to create a socket and steal future connections from them.
56+
*/
57+
if (is_another_server_alive(path, opts)) {
58+
rollback_lock_file(&lock);
59+
errno = EADDRINUSE;
60+
return -2;
61+
}
62+
63+
/*
64+
* Create and bind to a Unix domain socket at "<path>".
65+
*/
66+
fd_socket = unix_stream_listen(path, opts);
67+
if (fd_socket < 0) {
68+
int saved_errno = errno;
69+
rollback_lock_file(&lock);
70+
errno = saved_errno;
71+
return -1;
72+
}
73+
74+
server_socket = xcalloc(1, sizeof(*server_socket));
75+
server_socket->path_socket = strdup(path);
76+
server_socket->fd_socket = fd_socket;
77+
lstat(path, &server_socket->st_socket);
78+
79+
*new_server_socket = server_socket;
80+
81+
/*
82+
* Always rollback (just delete) "<path>.lock" because we already created
83+
* "<path>" as a socket and do not want to commit_lock to do the atomic
84+
* rename trick.
85+
*/
86+
rollback_lock_file(&lock);
87+
88+
return 0;
89+
}
90+
91+
void unix_ss_free(struct unix_ss_socket *server_socket)
92+
{
93+
if (!server_socket)
94+
return;
95+
96+
if (server_socket->fd_socket >= 0) {
97+
if (!unix_ss_was_stolen(server_socket))
98+
unlink(server_socket->path_socket);
99+
close(server_socket->fd_socket);
100+
}
101+
102+
free(server_socket->path_socket);
103+
free(server_socket);
104+
}
105+
106+
int unix_ss_was_stolen(struct unix_ss_socket *server_socket)
107+
{
108+
struct stat st_now;
109+
110+
if (!server_socket)
111+
return 0;
112+
113+
if (lstat(server_socket->path_socket, &st_now) == -1)
114+
return 1;
115+
116+
if (st_now.st_ino != server_socket->st_socket.st_ino)
117+
return 1;
118+
if (st_now.st_dev != server_socket->st_socket.st_dev)
119+
return 1;
120+
121+
if (!S_ISSOCK(st_now.st_mode))
122+
return 1;
123+
124+
return 0;
125+
}

unix-stream-server.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#ifndef UNIX_STREAM_SERVER_H
2+
#define UNIX_STREAM_SERVER_H
3+
4+
#include "unix-socket.h"
5+
6+
struct unix_ss_socket {
7+
char *path_socket;
8+
struct stat st_socket;
9+
int fd_socket;
10+
};
11+
12+
/*
13+
* Create a Unix Domain Socket at the given path under the protection
14+
* of a '.lock' lockfile.
15+
*
16+
* Returns 0 on success, -1 on error, -2 if socket is in use.
17+
*/
18+
int unix_ss_create(const char *path,
19+
const struct unix_stream_listen_opts *opts,
20+
long timeout_ms,
21+
struct unix_ss_socket **server_socket);
22+
23+
/*
24+
* Close and delete the socket.
25+
*/
26+
void unix_ss_free(struct unix_ss_socket *server_socket);
27+
28+
/*
29+
* Return 1 if the inode of the pathname to our socket changes.
30+
*/
31+
int unix_ss_was_stolen(struct unix_ss_socket *server_socket);
32+
33+
#endif /* UNIX_STREAM_SERVER_H */

0 commit comments

Comments
 (0)