Merge "QProcess/Unix: add a RAII class to hold the argv and envp pointers"

This commit is contained in:
Thiago Macieira 2021-02-18 14:45:02 -08:00 committed by Qt CI Bot
commit 35e9c21793

View File

@ -174,6 +174,19 @@ struct ChildError
char function[8];
};
// Used for argv and envp arguments to execve()
struct CharPointerList
{
std::unique_ptr<char *[]> pointers;
CharPointerList(const QString &argv0, const QStringList &args);
explicit CharPointerList(const QProcessEnvironmentPrivate *env);
private:
QByteArray data;
void updatePointers(qsizetype count);
};
struct QProcessPoller
{
QProcessPoller(const QProcessPrivate &proc);
@ -209,6 +222,62 @@ int QProcessPoller::poll(const QDeadlineTimer &deadline)
{
return qt_poll_msecs(pfds, n_pfds, deadline.remainingTime());
}
CharPointerList::CharPointerList(const QString &program, const QStringList &args)
{
qsizetype count = 1 + args.size();
pointers.reset(new char *[count + 1]);
pointers[count] = nullptr;
// we abuse the pointer array to store offsets first (QByteArray will
// reallocate, after all)
pointers[0] = reinterpret_cast<char *>(0);
data = QFile::encodeName(program);
data += '\0';
const auto end = args.end();
auto it = args.begin();
for (qsizetype i = 1; it != end; ++it, ++i) {
pointers[i] = reinterpret_cast<char *>(data.size());
data += QFile::encodeName(*it);
data += '\0';
}
updatePointers(count);
}
CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment)
{
if (!environment)
return;
const QProcessEnvironmentPrivate::Map &env = environment->vars;
qsizetype count = env.size();
pointers.reset(new char *[count + 1]);
pointers[count] = nullptr;
const auto end = env.end();
auto it = env.begin();
for (qsizetype i = 0; it != end; ++it, ++i) {
// we abuse the pointer array to store offsets first (QByteArray will
// reallocate, after all)
pointers[i] = reinterpret_cast<char *>(data.size());
data += it.key();
data += '=';
data += it->bytes();
data += '\0';
}
updatePointers(count);
}
void CharPointerList::updatePointers(qsizetype count)
{
char *const base = const_cast<char *>(data.constBegin());
for (qsizetype i = 0; i < count; ++i)
pointers[i] = base + qptrdiff(pointers[i]);
}
} // anonymous namespace
static bool qt_pollfd_check(const pollfd &pfd, short revents)
@ -402,31 +471,6 @@ static QString resolveExecutable(const QString &program)
return program;
}
static char **_q_dupEnvironment(const QProcessEnvironmentPrivate::Map &environment, int *envc)
{
*envc = 0;
if (environment.isEmpty())
return nullptr;
char **envp = new char *[environment.count() + 2];
envp[environment.count()] = nullptr;
envp[environment.count() + 1] = nullptr;
auto it = environment.constBegin();
const auto end = environment.constEnd();
for ( ; it != end; ++it) {
QByteArray key = it.key();
QByteArray value = it.value().bytes();
key.reserve(key.length() + 1 + value.length());
key.append('=');
key.append(value);
envp[(*envc)++] = ::strdup(key.constData());
}
return envp;
}
void QProcessPrivate::startProcess()
{
Q_Q(QProcess);
@ -455,25 +499,9 @@ void QProcessPrivate::startProcess()
// Start the process (platform dependent)
q->setProcessState(QProcess::Starting);
// Create argument list with right number of elements, and set the final
// one to 0.
char **argv = new char *[arguments.count() + 2];
argv[arguments.count() + 1] = nullptr;
// Resolve the program name
QString resolvedProgram = resolveExecutable(program);
argv[0] = ::strdup(QFile::encodeName(resolvedProgram).constData());
// Add every argument to the list
for (int i = 0; i < arguments.count(); ++i)
argv[i + 1] = ::strdup(QFile::encodeName(arguments.at(i)).constData());
// Duplicate the environment.
int envc = 0;
char **envp = nullptr;
if (environment.d.constData()) {
envp = _q_dupEnvironment(environment.d.constData()->vars, &envc);
}
// Prepare the arguments and the environment
const CharPointerList argv(resolveExecutable(program), arguments);
const CharPointerList envp(environment.d.constData());
// Encode the working directory if it's non-empty, otherwise just pass 0.
const char *workingDirPtr = nullptr;
@ -493,16 +521,6 @@ void QProcessPrivate::startProcess()
pid_t childPid;
forkfd = ::forkfd(ffdflags , &childPid);
int lastForkErrno = errno;
if (forkfd != FFD_CHILD_PROCESS) {
// Parent process.
// Clean up duplicated memory.
for (int i = 0; i <= arguments.count(); ++i)
free(argv[i]);
for (int i = 0; i < envc; ++i)
free(envp[i]);
delete [] argv;
delete [] envp;
}
if (forkfd == -1) {
// Cleanup, report error and return
@ -518,7 +536,7 @@ void QProcessPrivate::startProcess()
// Start the child.
if (forkfd == FFD_CHILD_PROCESS) {
execChild(workingDirPtr, argv, envp);
execChild(workingDirPtr, argv.pointers.get(), envp.pointers.get());
::_exit(-1);
}
@ -915,6 +933,9 @@ bool QProcessPrivate::startDetached(qint64 *pid)
return false;
}
const CharPointerList argv(resolveExecutable(program), arguments);
const CharPointerList envp(environment.d.constData());
pid_t childPid = fork();
if (childPid == 0) {
::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored
@ -940,23 +961,10 @@ bool QProcessPrivate::startDetached(qint64 *pid)
// Render channels configuration.
commitChannels();
char **argv = new char *[arguments.size() + 2];
for (int i = 0; i < arguments.size(); ++i)
argv[i + 1] = ::strdup(QFile::encodeName(arguments.at(i)).constData());
argv[0] = ::strdup(QFile::encodeName(resolveExecutable(program)).constData());
argv[arguments.size() + 1] = nullptr;
// Duplicate the environment.
int envc = 0;
char **envp = nullptr;
if (environment.d.constData()) {
envp = _q_dupEnvironment(environment.d.constData()->vars, &envc);
}
if (envp)
qt_safe_execve(argv[0], argv, envp);
if (envp.pointers)
qt_safe_execve(argv.pointers[0], argv.pointers.get(), envp.pointers.get());
else
qt_safe_execv(argv[0], argv);
qt_safe_execv(argv.pointers[0], argv.pointers.get());
reportFailed("execv: ");
} else if (doubleForkPid == -1) {