libutil: give Pid proper resource semantics

copy-constructing or assigning from pid_t can easily lead to duplicate
Pid instances for the same process if a pid_t was used carelessly, and
Pid itself was copy-constructible. both could cause surprising results
such as killing processes twice (which could become very problemantic,
but luckily modern systems don't reuse PIDs all that quickly), or more
than one piece of the code believing it owns a process when neither do

Change-Id: Ifea7445f84200b34c1a1d0acc2cdffe0f01e20c6
This commit is contained in:
eldritch horrors 2024-04-05 21:02:04 +02:00
parent b43a2e84c4
commit 3d155fc509
8 changed files with 37 additions and 40 deletions

View file

@ -354,7 +354,7 @@ RunPager::RunPager()
Pipe toPager;
toPager.create();
pid = startProcess([&]() {
pid = Pid{startProcess([&]() {
if (dup2(toPager.readSide.get(), STDIN_FILENO) == -1)
throw SysError("dupping stdin");
if (!getenv("LESS"))
@ -366,7 +366,7 @@ RunPager::RunPager()
execlp("less", "less", nullptr);
execlp("more", "more", nullptr);
throw SysError("executing '%1%'", pager);
});
})};
pid.setKillSignal(SIGINT);
std_out = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0);

View file

@ -35,7 +35,7 @@ HookInstance::HookInstance()
builderOut.create();
/* Fork the hook. */
pid = startProcess([&]() {
pid = Pid{startProcess([&]() {
if (dup2(fromHook.writeSide.get(), STDERR_FILENO) == -1)
throw SysError("cannot pipe standard error into log file");
@ -60,7 +60,7 @@ HookInstance::HookInstance()
execv(buildHook.c_str(), stringsToCharPtrs(args).data());
throw SysError("executing '%s'", buildHook);
});
})};
pid.setSeparatePG(true);
fromHook.writeSide.reset();

View file

@ -906,7 +906,7 @@ void LocalDerivationGoal::startBuilder()
Pipe sendPid;
sendPid.create();
Pid helper = startProcess([&]() {
Pid helper{startProcess([&]() {
sendPid.readSide.close();
/* We need to open the slave early, before
@ -934,7 +934,7 @@ void LocalDerivationGoal::startBuilder()
writeFull(sendPid.writeSide.get(), fmt("%d\n", child));
_exit(0);
});
})};
sendPid.writeSide.close();
@ -951,7 +951,7 @@ void LocalDerivationGoal::startBuilder()
auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get()));
assert(ss.size() == 1);
pid = string2Int<pid_t>(ss[0]).value();
pid = Pid{string2Int<pid_t>(ss[0]).value()};
if (usingUserNamespace) {
/* Set the UID/GID mapping of the builder's user namespace
@ -1010,10 +1010,10 @@ void LocalDerivationGoal::startBuilder()
} else
#endif
{
pid = startProcess([&]() {
pid = Pid{startProcess([&]() {
openSlave();
runChild();
});
})};
}
/* parent */

View file

@ -70,7 +70,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string
}
Finally cleanup = [&]() { logger->resume(); };
conn->sshPid = startProcess([&]() {
conn->sshPid = Pid{startProcess([&]() {
restoreProcessContext();
close(in.writeSide.get());
@ -99,7 +99,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string
// could not exec ssh/bash
throw SysError("unable to execute '%s'", args.front());
}, options);
}, options)};
in.readSide.reset();
@ -147,7 +147,7 @@ Path SSHMaster::startMaster()
if (isMasterRunning())
return state->socketPath;
state->sshMaster = startProcess([&]() {
state->sshMaster = Pid{startProcess([&]() {
restoreProcessContext();
close(out.readSide.get());
@ -160,7 +160,7 @@ Path SSHMaster::startMaster()
execvp(args.begin()->c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to execute '%s'", args.front());
}, options);
}, options)};
out.writeSide.reset();

View file

@ -94,12 +94,7 @@ bool userNamespacesSupported()
static auto res = [&]() -> bool
{
try {
Pid pid = startProcess([&]()
{
_exit(0);
}, {
.cloneFlags = CLONE_NEWUSER
});
Pid pid{startProcess([&]() { _exit(0); }, {.cloneFlags = CLONE_NEWUSER})};
auto r = pid.wait();
assert(!r);
@ -120,8 +115,7 @@ bool mountAndPidNamespacesSupported()
{
try {
Pid pid = startProcess([&]()
{
Pid pid{startProcess([&]() {
/* Make sure we don't remount the parent's /proc. */
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1)
_exit(1);
@ -136,7 +130,7 @@ bool mountAndPidNamespacesSupported()
_exit(0);
}, {
.cloneFlags = CLONE_NEWNS | CLONE_NEWPID | (userNamespacesSupported() ? CLONE_NEWUSER : 0)
});
})};
if (pid.wait()) {
debug("PID namespaces do not work on this system: cannot remount /proc");

View file

@ -35,9 +35,19 @@ Pid::Pid()
}
Pid::Pid(pid_t pid)
: pid(pid)
Pid::Pid(Pid && other) : pid(other.pid), separatePG(other.separatePG), killSignal(other.killSignal)
{
other.pid = -1;
}
Pid & Pid::operator=(Pid && other)
{
Pid tmp(std::move(other));
std::swap(pid, tmp.pid);
std::swap(separatePG, tmp.separatePG);
std::swap(killSignal, tmp.killSignal);
return *this;
}
@ -47,14 +57,6 @@ Pid::~Pid() noexcept(false)
}
void Pid::operator =(pid_t pid)
{
if (this->pid != -1 && this->pid != pid) kill();
this->pid = pid;
killSignal = SIGKILL; // reset signal to default
}
int Pid::kill()
{
assert(pid != -1);
@ -125,7 +127,7 @@ void killUser(uid_t uid)
users to which the current process can send signals. So we
fork a process, switch to uid, and send a mass kill. */
Pid pid = startProcess([&]() {
Pid pid{startProcess([&]() {
if (setuid(uid) == -1)
throw SysError("setting uid");
@ -147,7 +149,7 @@ void killUser(uid_t uid)
}
_exit(0);
});
})};
int status = pid.wait();
if (status != 0)
@ -288,7 +290,7 @@ void runProgram2(const RunOptions & options)
}
/* Fork. */
Pid pid = startProcess([&]() {
Pid pid{startProcess([&]() {
if (options.environment)
replaceEnv(*options.environment);
if (options.standardOut && dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
@ -322,7 +324,7 @@ void runProgram2(const RunOptions & options)
execv(options.program.c_str(), stringsToCharPtrs(args_).data());
throw SysError("executing '%1%'", options.program);
}, processOptions);
}, processOptions)};
out.writeSide.close();

View file

@ -26,9 +26,10 @@ class Pid
int killSignal = SIGKILL;
public:
Pid();
Pid(pid_t pid);
explicit Pid(pid_t pid): pid(pid) {}
Pid(Pid && other);
Pid & operator=(Pid && other);
~Pid() noexcept(false);
void operator =(pid_t pid);
explicit operator bool() const { return pid != -1; }
int kill();
int wait();

View file

@ -65,7 +65,7 @@ static void bindConnectProcHelper(
if (path.size() + 1 >= sizeof(addr.sun_path)) {
Pipe pipe;
pipe.create();
Pid pid = startProcess([&] {
Pid pid{startProcess([&] {
try {
pipe.readSide.close();
Path dir = dirOf(path);
@ -83,7 +83,7 @@ static void bindConnectProcHelper(
} catch (...) {
writeFull(pipe.writeSide.get(), "-1\n");
}
});
})};
pipe.writeSide.close();
auto errNo = string2Int<int>(chomp(drainFD(pipe.readSide.get())));
if (!errNo || *errNo == -1)