Я зову execvp()
с намеренно неправильным аргументом в fork()
Эд ребенок. errno
номер правильно установлен на ENOENT
в дочернем процессе. Затем я завершаю дочерний процесс _exit(errno);
,
Мой основной процесс звонки wait()
, Когда я проверяю возвращенный статус с WIFEXITED
а также WEXITSTATUS
я всегда получить EINVAL
для первого вызова. Все остальные вызовы возвращают правильный ENOENT
код.
Я не могу объяснить это поведение. Ниже приведена полная функция, которая выполняет все вышеперечисленные действия, но немного сложнее.
QVariantMap
System::exec(const QString & prog, const QStringList & args)
{
pid_t pid = fork();
if (pid == 0) {
int cargs_len = args.length() + 2;
char * cargs[cargs_len];
cargs[cargs_len - 1] = NULL;
QByteArrayList as;
as.push_back(prog.toLocal8Bit());
std::transform(args.begin(), args.end(), std::back_inserter(as),
[](const QString & s) { return s.toLocal8Bit(); });
for (int i = 0; i < as.length(); ++i) {
cargs[i] = as[i].data();
}
execvp(cargs[0], cargs);
// in case execvp fails, terminate the child process immediately
qDebug() << "(" << errno << ") " << strerror(errno); // <----------
_exit(errno);
} else if (pid < 0) {
goto fail;
} else {
sigset_t mask;
sigset_t orig_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
goto fail;
}
struct timespec timeout;
timeout.tv_sec = 0;
timeout.tv_nsec = 10 * 1000 * 1000;
while (true) {
int ret = sigtimedwait(&mask, NULL, &timeout);
if (ret < 0) {
if (errno == EAGAIN) {
// timeout
goto win;
} else {
// error
goto fail;
}
} else {
if (errno == EINTR) {
// not SIGCHLD
continue;
} else {
int status = 0;
if (wait(&status) == pid) {
if (WIFEXITED(status)) {
return { { "error", strerror(WEXITSTATUS(status)) } };
} else {
goto fail;
}
} else {
goto fail;
}
}
}
}
}
win:
return {};
fail:
return { { "error", strerror(errno) } };
}
Оказывается, что удаление строки с qDebug()
вызов заставляет проблему уйти. Почему добавление отладочного вызова меняет поведение программы?
qDebug() << "(" << errno << ") " << strerror(errno); _exit(errno);
Практически любой вызов стандартной библиотечной функции может изменить errno
, Вероятно, что qDebug
вызывает некоторые функции ввода / вывода, которые устанавливают errno
или, может быть, даже <<
Оператор ввода / вывода. errno
не изменяется большинством успешных звонков, но чем выше уровень, тем меньше вы можете знать, что под капотом нет нормальных неудавшихся звонков. Так что ценность errno
что вы печатаете не ценность errno
что вы передаете _exit
,
Как общий принцип с errno
Если вы делаете что-то более сложное, чем просто распечатываете его один раз, сохраните значение в переменной, прежде чем делать что-либо еще.
Как уже отмечалось в комментариях, обратите внимание, что большинство систем Unix, включая все распространенные, передают только 8-битные значения как состояния выхода, но errno
может быть больше 255. Например, если вы запустите эту программу в системе, где 256 — это возможный код ошибки, вызов _exit(256)
приведет к тому, что вызывающая сторона увидит код возврата 0 и, следовательно, неверно поверит в успех.
Обычно достаточно свернуть все значения ошибок до успеха / неудачи. Если вам нужно различать больше, убедитесь, что информация, которую вы передаете через exit
/wait
подходит в диапазоне 0–255.
int exec_error = errno;
qDebug() << "(" << exec_error << ") " << strerror(exec_error);
_exit(!!exec_error);