Skip to content

Commit eab6595

Browse files
authored
Merge pull request #9048 from the-horo/closefrom-spawn-process
std/process: Default to libc closefrom in spawnProcessPosix
2 parents 61bd59c + 48d581a commit eab6595

File tree

1 file changed

+102
-47
lines changed

1 file changed

+102
-47
lines changed

std/process.d

Lines changed: 102 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,7 @@ version (Posix) private enum InternalError : ubyte
880880
doubleFork,
881881
malloc,
882882
preExec,
883+
closefds_dup2,
883884
}
884885

885886
/*
@@ -1008,7 +1009,7 @@ private Pid spawnProcessPosix(scope const(char[])[] args,
10081009
if (config.flags & Config.Flags.detached)
10091010
close(pidPipe[0]);
10101011
close(forkPipe[0]);
1011-
immutable forkPipeOut = forkPipe[1];
1012+
auto forkPipeOut = forkPipe[1];
10121013
immutable pidPipeOut = pidPipe[1];
10131014

10141015
// Set the working directory.
@@ -1042,56 +1043,106 @@ private Pid spawnProcessPosix(scope const(char[])[] args,
10421043

10431044
if (!(config.flags & Config.Flags.inheritFDs))
10441045
{
1045-
// NOTE: malloc() and getrlimit() are not on the POSIX async
1046-
// signal safe functions list, but practically this should
1047-
// not be a problem. Java VM and CPython also use malloc()
1048-
// in its own implementation via opendir().
1049-
import core.stdc.stdlib : malloc;
1050-
import core.sys.posix.poll : pollfd, poll, POLLNVAL;
1051-
import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE;
1052-
1053-
// Get the maximum number of file descriptors that could be open.
1054-
rlimit r;
1055-
if (getrlimit(RLIMIT_NOFILE, &r) != 0)
1056-
{
1057-
abortOnError(forkPipeOut, InternalError.getrlimit, .errno);
1058-
}
1059-
immutable maxDescriptors = cast(int) r.rlim_cur;
1060-
1061-
// The above, less stdin, stdout, and stderr
1062-
immutable maxToClose = maxDescriptors - 3;
1046+
version (FreeBSD)
1047+
import core.sys.freebsd.unistd : closefrom;
1048+
else version (OpenBSD)
1049+
import core.sys.openbsd.unistd : closefrom;
10631050

1064-
// Call poll() to see which ones are actually open:
1065-
auto pfds = cast(pollfd*) malloc(pollfd.sizeof * maxToClose);
1066-
if (pfds is null)
1067-
{
1068-
abortOnError(forkPipeOut, InternalError.malloc, .errno);
1069-
}
1070-
foreach (i; 0 .. maxToClose)
1051+
static if (!__traits(compiles, closefrom))
10711052
{
1072-
pfds[i].fd = i + 3;
1073-
pfds[i].events = 0;
1074-
pfds[i].revents = 0;
1075-
}
1076-
if (poll(pfds, maxToClose, 0) >= 0)
1077-
{
1078-
foreach (i; 0 .. maxToClose)
1079-
{
1080-
// don't close pipe write end
1081-
if (pfds[i].fd == forkPipeOut) continue;
1082-
// POLLNVAL will be set if the file descriptor is invalid.
1083-
if (!(pfds[i].revents & POLLNVAL)) close(pfds[i].fd);
1084-
}
1085-
}
1086-
else
1087-
{
1088-
// Fall back to closing everything.
1089-
foreach (i; 3 .. maxDescriptors)
1090-
{
1091-
if (i == forkPipeOut) continue;
1092-
close(i);
1053+
// FIXME: This implementation crashes the system when RLIMIT_NOFILE
1054+
// has a big value. For a possible solution see:
1055+
// https://github.com/dlang/phobos/pull/8990
1056+
void fallback (int lowfd) {
1057+
// NOTE: malloc() and getrlimit() are not on the POSIX async
1058+
// signal safe functions list, but practically this should
1059+
// not be a problem. Java VM and CPython also use malloc()
1060+
// in its own implementation via opendir().
1061+
import core.stdc.stdlib : malloc;
1062+
import core.sys.posix.poll : pollfd, poll, POLLNVAL;
1063+
import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE;
1064+
1065+
// Get the maximum number of file descriptors that could be open.
1066+
rlimit r;
1067+
if (getrlimit(RLIMIT_NOFILE, &r) != 0)
1068+
{
1069+
abortOnError(forkPipeOut, InternalError.getrlimit, .errno);
1070+
}
1071+
immutable maxDescriptors = cast(int) r.rlim_cur;
1072+
1073+
immutable maxToClose = maxDescriptors - lowfd;
1074+
1075+
// Call poll() to see which ones are actually open:
1076+
auto pfds = cast(pollfd*) malloc(pollfd.sizeof * maxToClose);
1077+
if (pfds is null)
1078+
{
1079+
abortOnError(forkPipeOut, InternalError.malloc, .errno);
1080+
}
1081+
foreach (i; 0 .. maxToClose)
1082+
{
1083+
pfds[i].fd = i + lowfd;
1084+
pfds[i].events = 0;
1085+
pfds[i].revents = 0;
1086+
}
1087+
if (poll(pfds, maxToClose, 0) >= 0)
1088+
{
1089+
foreach (i; 0 .. maxToClose)
1090+
{
1091+
// POLLNVAL will be set if the file descriptor is invalid.
1092+
if (!(pfds[i].revents & POLLNVAL)) close(pfds[i].fd);
1093+
}
1094+
}
1095+
else
1096+
{
1097+
// Fall back to closing everything.
1098+
foreach (i; lowfd .. maxDescriptors)
1099+
{
1100+
close(i);
1101+
}
1102+
}
10931103
}
1104+
1105+
// closefrom may not be available on the version of glibc we build against.
1106+
// Until we find a way to perform this check we will try to use dlsym to
1107+
// check for the function. See: https://github.com/dlang/phobos/pull/9048
1108+
version (CRuntime_Glibc)
1109+
void closefrom (int lowfd) {
1110+
static bool tryGlibcClosefrom (int lowfd) {
1111+
import core.sys.posix.dlfcn : dlopen, dlclose, dlsym, dlerror, RTLD_LAZY;
1112+
1113+
void *handle = dlopen("libc.so.6", RTLD_LAZY);
1114+
if (!handle)
1115+
return false;
1116+
scope(exit) dlclose(handle);
1117+
1118+
// Clear errors
1119+
dlerror();
1120+
alias closefromT = extern(C) void function(int) @nogc @system nothrow;
1121+
auto closefrom = cast(closefromT) dlsym(handle, "closefrom");
1122+
if (dlerror())
1123+
return false;
1124+
1125+
closefrom(lowfd);
1126+
return true;
1127+
}
1128+
1129+
if (!tryGlibcClosefrom(lowfd))
1130+
fallback(lowfd);
1131+
}
1132+
else
1133+
alias closefrom = fallback;
10941134
}
1135+
1136+
// We need to close all open file descriptors excluding std{in,out,err}
1137+
// and forkPipeOut because we still need it.
1138+
// Since the various libc's provide us with `closefrom` move forkPipeOut
1139+
// to position 3, right after STDERR_FILENO, and close all FDs following that.
1140+
if (dup2(forkPipeOut, 3) == -1)
1141+
abortOnError(forkPipeOut, InternalError.closefds_dup2, .errno);
1142+
forkPipeOut = 3;
1143+
// forkPipeOut needs to be closed after we call `exec`.
1144+
setCLOEXEC(forkPipeOut, true);
1145+
closefrom(forkPipeOut + 1);
10951146
}
10961147
else // This is already done if we don't inherit descriptors.
10971148
{
@@ -1205,6 +1256,10 @@ private Pid spawnProcessPosix(scope const(char[])[] args,
12051256
case InternalError.preExec:
12061257
errorMsg = "Failed to execute preExecFunction or preExecDelegate";
12071258
break;
1259+
case InternalError.closefds_dup2:
1260+
assert(!(config.flags & Config.Flags.inheritFDs));
1261+
errorMsg = "Failed to close inherited file descriptors";
1262+
break;
12081263
case InternalError.noerror:
12091264
assert(false);
12101265
}

0 commit comments

Comments
 (0)