Skip to content

Commit b483188

Browse files
yfeldblumfacebook-github-bot
authored andcommitted
Subprocess can change cgroup
Reviewed By: Gownta Differential Revision: D70378826 fbshipit-source-id: e2f1b5c57f74b1bd87bffd23e57c31e22a785588
1 parent f234ea6 commit b483188

File tree

3 files changed

+271
-4
lines changed

3 files changed

+271
-4
lines changed

folly/Subprocess.cpp

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,17 @@ static Ret subprocess_libc_load(
138138
X(signal, signal) \
139139
X(sprintf, sprintf) \
140140
X(strtol, strtol) \
141-
X(vfork, vfork)
141+
X(vfork, vfork) \
142+
X(write, write)
142143

143144
#if defined(__BIONIC_INCLUDE_FORTIFY_HEADERS)
144-
#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_OPEN(X) X(open, __open_real)
145+
#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_OPEN(X) \
146+
X(open, __open_real) \
147+
X(openat, __openat_real)
145148
#else
146-
#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_OPEN(X) X(open, open)
149+
#define FOLLY_DETAIL_SUBPROCESS_LIBC_X_OPEN(X) \
150+
X(open, open) \
151+
X(openat, openat)
147152
#endif
148153

149154
#if defined(__linux__)
@@ -202,8 +207,20 @@ struct Subprocess::SpawnRawArgs {
202207
}
203208
};
204209

210+
template <typename T>
211+
struct AttrWithMeta {
212+
T value{};
213+
int* errout{};
214+
};
215+
216+
static char const* getCStrForNonEmpty(std::string const& str) {
217+
return str.empty() ? nullptr : str.c_str();
218+
}
219+
205220
// from options
206221
char const* childDir{};
222+
AttrWithMeta<int> linuxCGroupFd{-1, nullptr};
223+
AttrWithMeta<char const*> linuxCGroupPath{nullptr, nullptr};
207224
bool closeOtherFds{};
208225
#if defined(__linux__)
209226
cpu_set_t const* cpuSet{};
@@ -229,7 +246,12 @@ struct Subprocess::SpawnRawArgs {
229246
sigset_t oldSignals{};
230247

231248
explicit SpawnRawArgs(Scratch const& scratch, Options const& options)
232-
: childDir{options.childDir_.empty() ? nullptr : options.childDir_.c_str()},
249+
: childDir{getCStrForNonEmpty(options.childDir_)},
250+
linuxCGroupFd{
251+
options.linuxCGroupFd_.value, options.linuxCGroupFd_.errout},
252+
linuxCGroupPath{
253+
getCStrForNonEmpty(options.linuxCGroupPath_.value),
254+
options.linuxCGroupPath_.errout},
233255
closeOtherFds{options.closeOtherFds_},
234256
#if defined(__linux__)
235257
cpuSet{get_pointer(options.cpuSet_)},
@@ -391,6 +413,28 @@ Subprocess::Options& Subprocess::Options::fd(int fd, int action) {
391413
return *this;
392414
}
393415

416+
#if defined(__linux__)
417+
418+
Subprocess::Options& Subprocess::Options::setLinuxCGroupFd(
419+
int cgroupFd, std::shared_ptr<int> errout) {
420+
if (linuxCGroupFd_.value >= 0 || !linuxCGroupPath_.value.empty()) {
421+
throw std::runtime_error("setLinuxCGroup* called more than once");
422+
}
423+
linuxCGroupFd_ = {cgroupFd, std::move(errout)};
424+
return *this;
425+
}
426+
427+
Subprocess::Options& Subprocess::Options::setLinuxCGroupPath(
428+
const std::string& cgroupPath, std::shared_ptr<int> errout) {
429+
if (linuxCGroupFd_.value >= 0 || !linuxCGroupPath_.value.empty()) {
430+
throw std::runtime_error("setLinuxCGroup* called more than once");
431+
}
432+
linuxCGroupPath_ = {cgroupPath, std::move(errout)};
433+
return *this;
434+
}
435+
436+
#endif
437+
394438
Subprocess::Options& Subprocess::Options::addPrintPidToBuffer(span<char> buf) {
395439
if (buf.size() < kPidBufferMinSize) {
396440
throw std::invalid_argument("buf size too small");
@@ -695,6 +739,46 @@ pid_t Subprocess::spawnInternalDoFork(SpawnRawArgs const& args) {
695739
}
696740
FOLLY_POP_WARNING
697741

742+
FOLLY_DETAIL_SUBPROCESS_RAW
743+
int Subprocess::prepareChildDoOptionalError(int* errout) {
744+
if (errout) {
745+
*errout = errno;
746+
return 0;
747+
} else {
748+
return errno;
749+
}
750+
}
751+
752+
FOLLY_DETAIL_SUBPROCESS_RAW
753+
int Subprocess::prepareChildDoLinuxCGroup(SpawnRawArgs const& args) {
754+
auto cgroupPath = args.linuxCGroupPath;
755+
auto cgroupFd = args.linuxCGroupFd;
756+
if (nullptr != cgroupPath.value) {
757+
int fd = detail::subprocess_libc::open(
758+
cgroupPath.value, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
759+
if (-1 == fd) {
760+
return prepareChildDoOptionalError(cgroupPath.errout);
761+
}
762+
cgroupFd = {fd, cgroupPath.errout};
763+
}
764+
if (-1 != cgroupFd.value) {
765+
int fd = detail::subprocess_libc::openat(
766+
cgroupFd.value, "cgroup.procs", O_WRONLY | O_CLOEXEC);
767+
if (fd == -1) {
768+
return prepareChildDoOptionalError(cgroupFd.errout);
769+
}
770+
int rc = 0;
771+
do {
772+
constexpr char const buf = '0';
773+
rc = detail::subprocess_libc::write(fd, &buf, 1);
774+
} while (rc == -1 && errno == EINTR);
775+
if (rc == -1) {
776+
return prepareChildDoOptionalError(cgroupFd.errout);
777+
}
778+
}
779+
return 0;
780+
}
781+
698782
// If requested, close all other file descriptors. Don't close
699783
// any fds in options.fdActions_, and don't touch stdin, stdout, stderr.
700784
// Ignore errors.
@@ -778,6 +862,11 @@ int Subprocess::prepareChild(SpawnRawArgs const& args) {
778862
}
779863
}
780864

865+
// Move the child process into a linux cgroup, if one is given
866+
if (auto rc = prepareChildDoLinuxCGroup(args)) {
867+
return rc;
868+
}
869+
781870
// Change the working directory, if one is given
782871
if (args.childDir) {
783872
if (::chdir(args.childDir) == -1) {

folly/Subprocess.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,18 @@ class Subprocess {
545545
cpuSet_ = cpuSet;
546546
return *this;
547547
}
548+
549+
/*
550+
* setLinuxCGroup*
551+
* Takes a fd or a path to the cgroup dir. Only one may be provided.
552+
* Note that the cgroup filesystem may be mounted at any arbitrary point in
553+
* the filesystem hierarchy, and that different distributions may have their
554+
* own standard points. So just taking a cgroup name would be non-portable.
555+
*/
556+
Options& setLinuxCGroupFd(
557+
int cgroupFd, std::shared_ptr<int> errout = nullptr);
558+
Options& setLinuxCGroupPath(
559+
const std::string& cgroupPath, std::shared_ptr<int> errout = nullptr);
548560
#endif
549561

550562
Options& setUid(uid_t uid, std::shared_ptr<int> errout = nullptr) {
@@ -593,6 +605,8 @@ class Subprocess {
593605
// terminateOrKill() to kill the child process.
594606
TimeoutDuration::rep destroyBehavior_{DestroyBehaviorFatal};
595607
std::string childDir_; // "" keeps the parent's working directory
608+
AttrWithMeta<int> linuxCGroupFd_{-1, nullptr}; // -1 means no cgroup
609+
AttrWithMeta<std::string> linuxCGroupPath_{}; // empty means no cgroup
596610
#if defined(__linux__)
597611
int parentDeathSignal_{0};
598612
#endif
@@ -1056,6 +1070,8 @@ class Subprocess {
10561070
// Note that this runs after vfork(), so tread lightly.
10571071
// Returns 0 on success, or an errno value on failure.
10581072
static int prepareChild(SpawnRawArgs const& args);
1073+
static int prepareChildDoOptionalError(int* errout);
1074+
static int prepareChildDoLinuxCGroup(SpawnRawArgs const& args);
10591075
static int runChild(SpawnRawArgs const& args);
10601076

10611077
// Closes fds inherited from parent in child process

folly/test/SubprocessTest.cpp

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")
4444

4545
using namespace folly;
4646
using namespace std::chrono_literals;
47+
using namespace std::string_literals;
4748
using namespace std::string_view_literals;
4849

4950
namespace std::chrono {
@@ -1103,4 +1104,165 @@ TEST(SetUserGroupId, CanOverrideAndReportFailure) {
11031104
EXPECT_EQ(fmt::format("{}\t{}\t{}\t{}", gid, egid, gid, gid), gidline);
11041105
}
11051106

1107+
TEST(SetLinuxCGroup, CanSetCGroupFdAbsent) {
1108+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1109+
auto cgdirfd = ::open(cgdir.path().native().c_str(), O_DIRECTORY | O_CLOEXEC);
1110+
auto cgdirfdGuard = folly::makeGuard([&] { ::close(cgdirfd); });
1111+
auto options = Subprocess::Options();
1112+
options.setLinuxCGroupFd(cgdirfd);
1113+
EXPECT_THROW(
1114+
Subprocess(std::vector{"/bin/true"s}, options), SubprocessSpawnError);
1115+
}
1116+
1117+
TEST(SetLinuxCGroup, CanSetCGroupFdAbsentIntoErrnum) {
1118+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1119+
auto cgdirfd = ::open(cgdir.path().native().c_str(), O_DIRECTORY | O_CLOEXEC);
1120+
auto cgdirfdGuard = folly::makeGuard([&] { ::close(cgdirfd); });
1121+
auto options = Subprocess::Options();
1122+
std::shared_ptr<int> emptysp;
1123+
int errnum = 0;
1124+
options.setLinuxCGroupFd(cgdirfd, std::shared_ptr<int>{emptysp, &errnum});
1125+
Subprocess proc(std::vector{"/bin/true"s}, options);
1126+
EXPECT_EQ(ENOENT, errnum) << ::strerror(errnum);
1127+
proc.wait();
1128+
EXPECT_EQ(0, proc.returnCode().exitStatus());
1129+
}
1130+
1131+
TEST(SetLinuxCGroup, CanSetCGroupFdPresent) {
1132+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1133+
auto cgdirfd = ::open(cgdir.path().native().c_str(), O_DIRECTORY | O_CLOEXEC);
1134+
auto cgdirfdGuard = folly::makeGuard([&] { ::close(cgdirfd); });
1135+
auto cgprocs = cgdir.path() / "cgroup.procs";
1136+
::creat(cgprocs.native().c_str(), 0755); // rm'd with cgdir
1137+
auto options = Subprocess::Options();
1138+
options.setLinuxCGroupFd(cgdirfd);
1139+
Subprocess proc(std::vector{"/bin/true"s}, options);
1140+
std::string s;
1141+
EXPECT_TRUE(readFile(cgprocs.native().c_str(), s));
1142+
EXPECT_EQ("0", s);
1143+
proc.wait();
1144+
}
1145+
1146+
TEST(SetLinuxCGroup, CanSetCGroupFdPresentIntoErrnum) {
1147+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1148+
auto cgdirfd = ::open(cgdir.path().native().c_str(), O_DIRECTORY | O_CLOEXEC);
1149+
auto cgdirfdGuard = folly::makeGuard([&] { ::close(cgdirfd); });
1150+
auto cgprocs = cgdir.path() / "cgroup.procs";
1151+
::creat(cgprocs.native().c_str(), 0755); // rm'd with cgdir
1152+
auto options = Subprocess::Options();
1153+
std::shared_ptr<int> emptysp;
1154+
int errnum = 0;
1155+
options.setLinuxCGroupFd(cgdirfd, std::shared_ptr<int>{emptysp, &errnum});
1156+
Subprocess proc(std::vector{"/bin/true"s}, options);
1157+
EXPECT_EQ(0, errnum) << ::strerror(errnum);
1158+
std::string s;
1159+
EXPECT_TRUE(readFile(cgprocs.native().c_str(), s));
1160+
EXPECT_EQ("0", s);
1161+
proc.wait();
1162+
}
1163+
1164+
TEST(SetLinuxCGroup, CanSetCGroupFdPresentProcsNoOpen) {
1165+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1166+
auto cgprocs = cgdir.path() / "cgroup.procs";
1167+
::creat(cgprocs.native().c_str(), 0); // rm'd with cgdir
1168+
auto cgdirfd = ::open(cgdir.path().native().c_str(), O_DIRECTORY | O_CLOEXEC);
1169+
auto cgdirfdGuard = folly::makeGuard([&] { ::close(cgdirfd); });
1170+
auto options = Subprocess::Options();
1171+
options.setLinuxCGroupFd(cgdirfd);
1172+
EXPECT_THROW(
1173+
Subprocess(std::vector{"/bin/true"s}, options), SubprocessSpawnError);
1174+
}
1175+
1176+
TEST(SetLinuxCGroup, CanSetCGroupFdPresentProcsNoOpenIntoErrnum) {
1177+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1178+
auto cgprocs = cgdir.path() / "cgroup.procs";
1179+
::creat(cgprocs.native().c_str(), 0); // rm'd with cgdir
1180+
auto cgdirfd = ::open(cgdir.path().native().c_str(), O_DIRECTORY | O_CLOEXEC);
1181+
auto cgdirfdGuard = folly::makeGuard([&] { ::close(cgdirfd); });
1182+
auto options = Subprocess::Options();
1183+
std::shared_ptr<int> emptysp;
1184+
int errnum = 0;
1185+
options.setLinuxCGroupFd(cgdirfd, std::shared_ptr<int>{emptysp, &errnum});
1186+
Subprocess proc(std::vector{"/bin/true"s}, options);
1187+
EXPECT_EQ(EACCES, errnum) << ::strerror(errnum);
1188+
proc.wait();
1189+
EXPECT_EQ(0, proc.returnCode().exitStatus());
1190+
}
1191+
1192+
TEST(SetLinuxCGroup, CanSetCGroupPathAbsent) {
1193+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1194+
auto options = Subprocess::Options();
1195+
options.setLinuxCGroupPath(cgdir.path().string());
1196+
EXPECT_THROW(
1197+
Subprocess(std::vector{"/bin/true"s}, options), SubprocessSpawnError);
1198+
}
1199+
1200+
TEST(SetLinuxCGroup, CanSetCGroupPathAbsentIntoErrnum) {
1201+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1202+
auto options = Subprocess::Options();
1203+
std::shared_ptr<int> emptysp;
1204+
int errnum = 0;
1205+
options.setLinuxCGroupPath(
1206+
cgdir.path().string(), std::shared_ptr<int>{emptysp, &errnum});
1207+
Subprocess proc(std::vector{"/bin/true"s}, options);
1208+
EXPECT_EQ(ENOENT, errnum) << ::strerror(errnum);
1209+
proc.wait();
1210+
EXPECT_EQ(0, proc.returnCode().exitStatus());
1211+
}
1212+
1213+
TEST(SetLinuxCGroup, CanSetCGroupPathPresent) {
1214+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1215+
auto cgprocs = cgdir.path() / "cgroup.procs";
1216+
::creat(cgprocs.native().c_str(), 0755); // rm'd with cgdir
1217+
auto options = Subprocess::Options();
1218+
options.setLinuxCGroupPath(cgdir.path().string());
1219+
Subprocess proc(std::vector{"/bin/true"s}, options);
1220+
std::string s;
1221+
EXPECT_TRUE(readFile(cgprocs.native().c_str(), s));
1222+
EXPECT_EQ("0", s);
1223+
proc.wait();
1224+
}
1225+
1226+
TEST(SetLinuxCGroup, CanSetCGroupPathPresentIntoErrnum) {
1227+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1228+
auto cgprocs = cgdir.path() / "cgroup.procs";
1229+
::creat(cgprocs.native().c_str(), 0755); // rm'd with cgdir
1230+
auto options = Subprocess::Options();
1231+
std::shared_ptr<int> emptysp;
1232+
int errnum = 0;
1233+
options.setLinuxCGroupPath(
1234+
cgdir.path().string(), std::shared_ptr<int>{emptysp, &errnum});
1235+
Subprocess proc(std::vector{"/bin/true"s}, options);
1236+
EXPECT_EQ(0, errnum) << ::strerror(errnum);
1237+
std::string s;
1238+
EXPECT_TRUE(readFile(cgprocs.native().c_str(), s));
1239+
EXPECT_EQ("0", s);
1240+
proc.wait();
1241+
}
1242+
1243+
TEST(SetLinuxCGroup, CanSetCGroupPathPresentProcsNoOpen) {
1244+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1245+
auto cgprocs = cgdir.path() / "cgroup.procs";
1246+
::creat(cgprocs.native().c_str(), 0); // rm'd with cgdir
1247+
auto options = Subprocess::Options();
1248+
options.setLinuxCGroupPath(cgdir.path().string());
1249+
EXPECT_THROW(
1250+
Subprocess(std::vector{"/bin/true"s}, options), SubprocessSpawnError);
1251+
}
1252+
1253+
TEST(SetLinuxCGroup, CanSetCGroupPathPresentProcsNoOpenIntoErrnum) {
1254+
folly::test::TemporaryDirectory cgdir; // not a real cgroup dir
1255+
auto cgprocs = cgdir.path() / "cgroup.procs";
1256+
::creat(cgprocs.native().c_str(), 0); // rm'd with cgdir
1257+
auto options = Subprocess::Options();
1258+
std::shared_ptr<int> emptysp;
1259+
int errnum = 0;
1260+
options.setLinuxCGroupPath(
1261+
cgdir.path().string(), std::shared_ptr<int>{emptysp, &errnum});
1262+
Subprocess proc(std::vector{"/bin/true"s}, options);
1263+
EXPECT_EQ(EACCES, errnum) << ::strerror(errnum);
1264+
proc.wait();
1265+
EXPECT_EQ(0, proc.returnCode().exitStatus());
1266+
}
1267+
11061268
#endif

0 commit comments

Comments
 (0)