Skip to content

Commit 4e0149a

Browse files
yfeldblumfacebook-github-bot
authored andcommitted
an option for Subprocess child to print pid to a buffer
Summary: Let the child process print its pid into a provided buffer. The use-case is to that the child process can print its pid into zero'd storage in an environment variable, before the call to `execve`. Then the child process after `execve` will be able to see its own pid in that environment variable. The caller is responsible for preparing the environment-variable vector with the buffer. This is somewhat dangerous since the caller can provide any buffer, but it is simpler to implement the capability this way. This follows a feature of SystemD, which spawns child processes with their pid's in the environment variable `SYSTEMD_EXEC_PID`. Reviewed By: Gownta Differential Revision: D70577643 fbshipit-source-id: 7d600b74ae239e74c3c57e10510d232818b43744
1 parent efbf05d commit 4e0149a

File tree

4 files changed

+88
-2
lines changed

4 files changed

+88
-2
lines changed

folly/Subprocess.cpp

+24-2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ static Ret subprocess_libc_load(
136136
X(fcntl, fcntl) \
137137
X(pthread_sigmask, pthread_sigmask) \
138138
X(signal, signal) \
139+
X(sprintf, sprintf) \
139140
X(strtol, strtol) \
140141
X(vfork, vfork)
141142

@@ -190,9 +191,13 @@ __attribute__((constructor(101))) static void subprocess_libc_init() {
190191
struct Subprocess::SpawnRawArgs {
191192
struct Scratch {
192193
std::vector<std::pair<int, int>> fdActions;
194+
std::vector<char*> setPrintPidToBuffer;
193195

194196
explicit Scratch(Options const& options)
195-
: fdActions{options.fdActions_.begin(), options.fdActions_.end()} {
197+
: fdActions{options.fdActions_.begin(), options.fdActions_.end()},
198+
setPrintPidToBuffer{
199+
options.setPrintPidToBuffer_.begin(),
200+
options.setPrintPidToBuffer_.end()} {
196201
std::sort(fdActions.begin(), fdActions.end());
197202
}
198203
};
@@ -209,6 +214,8 @@ struct Subprocess::SpawnRawArgs {
209214
int parentDeathSignal{};
210215
bool processGroupLeader{};
211216
bool usePath{};
217+
char* const* setPrintPidToBufferData{};
218+
size_t setPrintPidToBufferSize{};
212219

213220
// assigned explicitly
214221
char const* const* argv{};
@@ -231,7 +238,9 @@ struct Subprocess::SpawnRawArgs {
231238
parentDeathSignal{options.parentDeathSignal_},
232239
#endif
233240
processGroupLeader{options.processGroupLeader_},
234-
usePath{options.usePath_} {
241+
usePath{options.usePath_},
242+
setPrintPidToBufferData{scratch.setPrintPidToBuffer.data()},
243+
setPrintPidToBufferSize{scratch.setPrintPidToBuffer.size()} {
235244
static_assert(std::is_standard_layout_v<Subprocess::SpawnRawArgs>);
236245
static_assert(std::is_trivially_destructible_v<Subprocess::SpawnRawArgs>);
237246
}
@@ -374,6 +383,14 @@ Subprocess::Options& Subprocess::Options::fd(int fd, int action) {
374383
return *this;
375384
}
376385

386+
Subprocess::Options& Subprocess::Options::addPrintPidToBuffer(span<char> buf) {
387+
if (buf.size() < kPidBufferMinSize) {
388+
throw std::invalid_argument("buf size too small");
389+
}
390+
setPrintPidToBuffer_.insert(buf.data());
391+
return *this;
392+
}
393+
377394
Subprocess::Subprocess() = default;
378395

379396
Subprocess::Subprocess(
@@ -833,6 +850,11 @@ int Subprocess::prepareChild(SpawnRawArgs const& args) {
833850
}
834851
}
835852

853+
for (size_t i = 0; i < args.setPrintPidToBufferSize; ++i) {
854+
auto buf = args.setPrintPidToBufferData[i];
855+
detail::subprocess_libc::sprintf(buf, "%d", getpid());
856+
}
857+
836858
// The user callback comes last, so that the child is otherwise all set up.
837859
if (args.dangerousPostForkPreExecCallback) {
838860
if (int error = (*args.dangerousPostForkPreExecCallback)()) {

folly/Subprocess.h

+12
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,15 @@ class Subprocess {
330330
friend class Subprocess;
331331

332332
public:
333+
// digits10 is the maximum number of decimal digits such that any number
334+
// up to this many decimal digits can always be represented in the given
335+
// integer type
336+
// but we need to have storage for the decimal representation of any
337+
// integer, so +1, and we need to have storage for the terminal null, so
338+
// again +1.
339+
static inline constexpr size_t kPidBufferMinSize =
340+
std::numeric_limits<pid_t>::digits10 + 2;
341+
333342
Options() {} // E.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58328
334343

335344
/**
@@ -538,6 +547,8 @@ class Subprocess {
538547
}
539548
#endif
540549

550+
Options& addPrintPidToBuffer(span<char> buf);
551+
541552
private:
542553
typedef boost::container::flat_map<int, int> FdMap;
543554
FdMap fdActions_;
@@ -559,6 +570,7 @@ class Subprocess {
559570
#if defined(__linux__)
560571
Optional<cpu_set_t> cpuSet_;
561572
#endif
573+
std::unordered_set<char*> setPrintPidToBuffer_;
562574
};
563575

564576
// Non-copyable, but movable

folly/test/BUCK

+2
Original file line numberDiff line numberDiff line change
@@ -1625,10 +1625,12 @@ cpp_unittest(
16251625
"//folly:format",
16261626
"//folly:string",
16271627
"//folly:subprocess",
1628+
"//folly/container:span",
16281629
"//folly/experimental/io:fs_util",
16291630
"//folly/gen:base",
16301631
"//folly/gen:file",
16311632
"//folly/gen:string",
1633+
"//folly/portability:gmock",
16321634
"//folly/portability:gtest",
16331635
"//folly/portability:unistd",
16341636
"//folly/testing:test_util",

folly/test/SubprocessTest.cpp

+50
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#include <sys/types.h>
2020

2121
#include <chrono>
22+
#include <string>
23+
#include <string_view>
24+
#include <vector>
2225

2326
#include <boost/container/flat_set.hpp>
2427
#include <glog/logging.h>
@@ -27,10 +30,12 @@
2730
#include <folly/FileUtil.h>
2831
#include <folly/Format.h>
2932
#include <folly/String.h>
33+
#include <folly/container/span.h>
3034
#include <folly/experimental/io/FsUtil.h>
3135
#include <folly/gen/Base.h>
3236
#include <folly/gen/File.h>
3337
#include <folly/gen/String.h>
38+
#include <folly/portability/GMock.h>
3439
#include <folly/portability/GTest.h>
3540
#include <folly/portability/Unistd.h>
3641
#include <folly/testing/TestUtil.h>
@@ -39,6 +44,7 @@ FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")
3944

4045
using namespace folly;
4146
using namespace std::chrono_literals;
47+
using namespace std::string_view_literals;
4248

4349
namespace std::chrono {
4450
template <typename Rep, typename Period>
@@ -920,3 +926,47 @@ TEST(KeepFileOpenSubprocessTest, KeepsFileOpen) {
920926
std::sort(std::begin(fds), std::end(fds));
921927
EXPECT_EQ(fmt::format("{}\n", fmt::join(fds, "\n")), p.first);
922928
}
929+
930+
static_assert(
931+
Subprocess::Options::kPidBufferMinSize ==
932+
std::numeric_limits<pid_t>::digits10 + 2);
933+
934+
TEST(WritePidIntoBufTest, WritesPidIntoBufTooSmall) {
935+
constexpr size_t size = Subprocess::Options::kPidBufferMinSize;
936+
char buf[size - 1] = {};
937+
auto options = Subprocess::Options();
938+
EXPECT_THROW(options.addPrintPidToBuffer(buf), std::invalid_argument);
939+
}
940+
941+
TEST(WritePidIntoBufTest, WritesPidIntoBuf) {
942+
constexpr size_t size = Subprocess::Options::kPidBufferMinSize;
943+
char buf[size] = {};
944+
std::memset(buf, 0xA5, size);
945+
auto options = Subprocess::Options().addPrintPidToBuffer(buf);
946+
Subprocess proc(std::vector<std::string>{"/bin/true"}, options);
947+
EXPECT_EQ(fmt::format("{}", proc.pid()), buf);
948+
proc.wait();
949+
}
950+
951+
TEST(WritePidIntoBufTest, WritesPidIntoBufExampleEnvVar) {
952+
// this test effectively duplicates WritesPidIntoBuf but may serve as a
953+
// reference for how to use this feature with environment-variable storage
954+
//
955+
// systemd does something like this:
956+
// https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#%24SYSTEMD_EXEC_PID
957+
constexpr size_t size = Subprocess::Options::kPidBufferMinSize;
958+
constexpr auto prefix = "FOLLY_TEST_SUBPROCESS_PID="sv;
959+
std::vector<std::string> env;
960+
env.emplace_back("FOLLY_TEST_GREETING=hello world");
961+
auto& var = env.emplace_back(prefix);
962+
var.resize(prefix.size() + size); // must be stable! no more changes to env!
963+
auto buf = folly::span{var}.subspan(prefix.size());
964+
auto options = Subprocess::Options().pipeStdout().addPrintPidToBuffer(buf);
965+
Subprocess proc(std::vector<std::string>{"/bin/env"}, options, nullptr, &env);
966+
auto pid = proc.pid();
967+
auto p = proc.communicate();
968+
proc.wait();
969+
std::vector<std::string_view> lines;
970+
folly::split('\n', p.first, lines);
971+
EXPECT_THAT(lines, testing::Contains(fmt::format("{}{}", prefix, pid)));
972+
}

0 commit comments

Comments
 (0)