Используя sudo, почему отсоединенный дочерний процесс завершается, когда родительский процесс умирает?

Я пишу программу, требующую прав администратора (программа обновления). В какой-то момент нам нужно запустить другую программу и позволить первой завершиться. Таким образом, родительский процесс создает ответвление, отделяется от своего дочернего процесса и завершает выполнение. Под пользователем root (например, с использованием «su») все работает как положено. Под обычным пользователем ничего особенного не происходит (кроме конечно записи файлов от имени root). Но при использовании sudo, как только родительский процесс завершает свое выполнение, его дочерний процесс также умирает.

Я написал минимальную программу для устранения этой проблемы:

#include <iostream>
#include <fstream>
#include <unistd.h>

int main()
{
    std::cout << "start: " << getuid() << std::endl;
    if (auto pid = fork() == 0)
    {
        setpgid(0, 0);
        std::cout << "fork has detached" << std::endl;
        sleep(1);
        std::ofstream test("/root/test.txt");
        std::cout << "fork: " << getuid() << " " << test.is_open() << std::endl;
    }
    else
    {
        setpgid(pid, 0);
        std::cout << "parent: " << getuid() << std::endl;
    }
    return 0;
}

Я ожидаю в качестве вывода:

$ sudo ./test_sudo
start: 0
parent: 0
fork has detached
$ fork: 0 1

Примечание: последнюю строку следует писать после подсказки.

Но с sudo я получаю только:

$ sudo ./test_sudo
start: 0
parent: 0
fork has detached
$

Если я добавлю вызов wait() в родительский элемент, дочерний элемент будет работать с sudo как и ожидалось. Итак, ребенок умирает вместе со своим родителем. Инструменты отладки, которые я пробовал, показывали только следы родительского элемента, как я могу отслеживать и отсоединенный дочерний элемент?

Почему использование sudo заставляет мою программу вести себя таким образом? Я могу обойти эту проблему в своем проекте, но меня интересует объяснение этого странного поведения.

В MacOS поведение идентично sudo или нет. Последние строки: fork: 0 0 или fork: 501 0

Basilevs 05.08.2024 16:14
strace --follow-forksдетям тоже покажет следы. Я предполагаю, что это не один из инструментов отладки, которые вы использовали?
Toby Speight 05.08.2024 16:24

Кстати, возможно, это не причина, но вы, вероятно, имели в виду if (auto pid = fork(); pid == 0), а не создание pid bool.

Toby Speight 05.08.2024 16:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
3
86
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Похоже, это поведение, специфичное для окружающей среды. Я тестировал Mac OS 12.7.4 zsh 5.8.1 (x86_64-apple-darwin21.0) и Ubuntu 20.04.6 LTS GNU bash, версия 5.0.17(1)-выпуск (x86_64-pc-linux-gnu) ) и каждый раз, когда присутствует последняя строка.

$ sudo ./a.out 
start: 0
parent: 0
fork has detached
$ fork: 0 1

Примечание: последняя строка задерживается и печатается после запроса.

Оболочку, которая не пытается восстановить строку, можно назвать «более простой», «минималистической» или, может быть, «умной», но определенно не «тупой».

William Pursell 05.08.2024 16:39

Да, именно этого я и ожидаю. Я добавил подсказку в свой вопрос, чтобы уточнить. Я использую Ubuntu 22.04.3 LTS GNU bash, версия 5.1.16(1)-релиз (x86_64-pc-linux-gnu), и я не могу получить последнюю строку с помощью sudo. Возможно, это зависит от некоторых настроек конфигурации.

Maxime Beluguet 05.08.2024 18:57

Ответ, получивший наибольшее количество голосов, очевидно, правильный и хорошо объяснен. Не издевайтесь над моей, которая понятия не имела.

Basilevs 05.08.2024 19:32
Ответ принят как подходящий

Это функция безопасности. sudo выделяет новый псевдотерминал перед выполнением указанной команды. Это сделано для того, чтобы избежать атак с использованием инъекций с терминалов, которые не контролируются напрямую sudo, см., например, рекомендации по безопасности SuSE.

В вашем случае это приводит к небольшой проблеме: ваша программа на самом деле все еще работает, но вы не увидите ее выходные данные: если вы измените финальный std::cout << ... в дочернем процессе следующим образом:

        std::ofstream log("/tmp/log");
        log << "fork: " << getuid() << " " << test.is_open() << std::endl;

вы увидите в /tmp/log, что программа все еще выполняется. Вы просто не увидели результат, потому что sudo освободил псевдотерминал после завершения родительского процесса. Итак, ваша запись std::cout попыталась записать в уже закрытый pty, что привело к отбрасыванию вывода. (std::cout.fail() было бы верно после строки std::cout << ..., потому что запись действительно не удалась.)

@MaximeBeluguet Часть X11 больше связана с xauth, чем с псевдотерминалом. Но, ИМХО, проще всего просто поддерживать работу программы, пока ей все еще требуется X11, и пользователь всегда может запустить команду sudo в фоновом режиме, если не хочет, чтобы она блокировала его терминал.

chris_se 05.08.2024 18:52

Когда вы запускаете sudo, процесс sudo становится лидером сеанса.

Когда родительский элемент завершает работу, sudo также завершает работу, что приводит к прекращению всех процессов (включая дочерние процессы sudo) сеанса.

Это противоречит тому факту, что все процессы в примере выполняются до завершения. Потеряется только вывод.

Basilevs 06.08.2024 10:39

@Basilevs Вы думаете, что std::cout << "fork: "... был запущен, потерян только вывод?

Philippe 06.08.2024 12:12

да. Я думаю, что это можно доказать, перенаправив вывод sudo в файл. Тогда Sudo должен быть достаточно умен, чтобы не выделять терминал.

Basilevs 06.08.2024 13:41

Также см. мой ответ, где более старые версии sudo по умолчанию ничего не завершали.

Basilevs 06.08.2024 13:47

Другие вопросы по теме