Skip to content

Commit b508727

Browse files
committed
Fix daemonded not starting on Mac (socket path too long)
The default profile now generates a ~50-character-long $TMPDIR by default (I assume it must have been shorter before). The singleton socket path we generate for daemonded ends up too long for the `bind` or `connect` APIs. (The socket path for clients is slightly shorter and just sneaks under the limit.) Work around this by using the Mac-specific pthread_chdir_np API to thread-safely change the CWD to the directory with the socket, accessing it with a relative path, then resetting the current directory to what it was before. Fixes #1390.
1 parent 8afd675 commit b508727

File tree

1 file changed

+92
-17
lines changed

1 file changed

+92
-17
lines changed

src/engine/framework/System.cpp

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,96 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
5454
namespace Sys {
5555
static Cvar::Cvar<bool> cvar_common_shutdownOnDrop("common.shutdownOnDrop", "shut down engine on game drop", Cvar::TEMPORARY, false);
5656

57+
static std::string singletonSocketPath;
5758
#ifdef _WIN32
5859
static HANDLE singletonSocket;
5960
#else
61+
#define SINGLETON_SOCKET_BASENAME "socket"
6062
static int singletonSocket;
6163
static FS::File lockFile;
6264
static bool haveSingletonLock = false;
65+
66+
static void FillSocketStruct(struct sockaddr_un &addr)
67+
{
68+
addr.sun_family = AF_UNIX;
69+
#ifdef __APPLE__
70+
Q_strncpyz(addr.sun_path, SINGLETON_SOCKET_BASENAME, sizeof(addr.sun_path));
71+
#else
72+
if (singletonSocketPath.size() > sizeof(addr.sun_path)) {
73+
Sys::Error("Singleton socket name '%s' is too long. Try configuring a shorter $TMPDIR",
74+
singletonSocketPath);
75+
}
76+
Q_strncpyz(addr.sun_path, singletonSocketPath.c_str(), sizeof(addr.sun_path));
6377
#endif
64-
static std::string singletonSocketPath;
78+
}
79+
80+
#ifdef __APPLE__
81+
// Secret Apple APIs. Chrome just declares them like this
82+
extern "C" {
83+
int pthread_chdir_np(const char* path);
84+
int pthread_fchdir_np(int fd);
85+
}
86+
87+
// These ChdirWrapper* functions return 0 on success or an errno on failure
88+
static int ChdirWrapperSingletonSocketConnect()
89+
{
90+
std::string dirName = FS::Path::DirName(singletonSocketPath);
91+
int error = 0;
92+
if (0 == pthread_chdir_np(dirName.c_str())) {
93+
struct sockaddr_un addr;
94+
FillSocketStruct(addr);
95+
if (0 != connect(singletonSocket, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))) {
96+
error = errno;
97+
}
98+
} else {
99+
// Assume the directory didn't exist. If it does but it is not accessible, we should
100+
// hit another error soon when trying to create the singleton socket
101+
error = ENOENT;
102+
}
103+
pthread_fchdir_np(-1); // reset CWD
104+
return error;
105+
}
106+
107+
static int ChdirWrapperSingletonSocketBind()
108+
{
109+
std::string dirName = FS::Path::DirName(singletonSocketPath);
110+
bool chdirSuccess = 0 == pthread_chdir_np(dirName.c_str());
111+
int error = 0;
112+
if (chdirSuccess) {
113+
struct sockaddr_un addr;
114+
FillSocketStruct(addr);
115+
if (0 != bind(singletonSocket, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))) {
116+
error = errno;
117+
}
118+
}
119+
pthread_fchdir_np(-1); // reset CWD
120+
if (!chdirSuccess) {
121+
Sys::Error("Failed to create or failed to access singleton socket directory '%s'", dirName);
122+
}
123+
return error;
124+
}
125+
#else // ! ifdef __APPLE__
126+
// TODO: supposedly there is an API that can be used to make chdir thread safe
127+
// on Linux too called "unshare"?
128+
static int ChdirWrapperSingletonSocketConnect()
129+
{
130+
struct sockaddr_un addr;
131+
FillSocketStruct(addr);
132+
return 0 == connect(singletonSocket, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))
133+
? 0
134+
: errno;
135+
}
136+
137+
static int ChdirWrapperSingletonSocketBind()
138+
{
139+
struct sockaddr_un addr;
140+
FillSocketStruct(addr);
141+
return 0 == bind(singletonSocket, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))
142+
? 0
143+
: errno;
144+
}
145+
#endif // ! ifdef __APPLE__
146+
#endif // ! ifdef _WIN32
65147

66148
// Get the path of a singleton socket
67149
std::string GetSingletonSocketPath()
@@ -78,7 +160,8 @@ std::string GetSingletonSocketPath()
78160
// We use a temporary directory rather that using the homepath because
79161
// socket paths are limited to about 100 characters. This also avoids issues
80162
// when the homepath is on a network filesystem.
81-
return FS::Path::Build(FS::Path::Build(FS::DefaultTempPath(), "." PRODUCT_NAME_LOWER + suffix), "socket");
163+
return FS::Path::Build(FS::Path::Build(
164+
FS::DefaultTempPath(), "." PRODUCT_NAME_LOWER + suffix), SINGLETON_SOCKET_BASENAME);
82165
#endif
83166
}
84167

@@ -120,11 +203,9 @@ static void CreateSingletonSocket()
120203
fchmod(singletonSocket, 0600);
121204
mkdir(dirName.c_str(), 0700);
122205

123-
struct sockaddr_un addr;
124-
addr.sun_family = AF_UNIX;
125-
Q_strncpyz(addr.sun_path, singletonSocketPath.c_str(), sizeof(addr.sun_path));
126-
if (bind(singletonSocket, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1)
127-
Sys::Error("Could not bind singleton socket at file: \"%s\", error: \"%s\"", singletonSocketPath, strerror(errno) );
206+
int bindErr = ChdirWrapperSingletonSocketBind();
207+
if (bindErr != 0)
208+
Sys::Error("Could not bind singleton socket at file: \"%s\", error: \"%s\"", singletonSocketPath, strerror(bindErr) );
128209

129210
if (listen(singletonSocket, SOMAXCONN) == -1)
130211
Sys::Error("Could not listen on singleton socket file \"%s\", error: \"%s\"", singletonSocketPath, strerror(errno) );
@@ -154,16 +235,10 @@ static bool ConnectSingletonSocket()
154235
if (singletonSocket == -1)
155236
Sys::Error("Could not create socket: %s", strerror(errno));
156237

157-
struct sockaddr_un addr;
158-
addr.sun_family = AF_UNIX;
159-
if (singletonSocketPath.size() > sizeof(addr.sun_path)) {
160-
Sys::Error("Singleton socket name '%s' is too long. Try configuring a shorter $TMPDIR",
161-
singletonSocketPath);
162-
}
163-
Q_strncpyz(addr.sun_path, singletonSocketPath.c_str(), sizeof(addr.sun_path));
164-
if (connect(singletonSocket, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1) {
165-
if (errno != ENOENT)
166-
Log::Warn("Could not connect to existing instance: %s", strerror(errno));
238+
int connectErr = ChdirWrapperSingletonSocketConnect();
239+
if (connectErr != 0) {
240+
if (connectErr != ENOENT)
241+
Log::Warn("Could not connect to existing instance: %s", strerror(connectErr));
167242
close(singletonSocket);
168243
return false;
169244
}

0 commit comments

Comments
 (0)