Дочерняя команда держит чужой файл: проверяем наследование дескрипторов

Приложение закрыло файл, но вспомогательная программа продолжает держать его открытым. Или блокировка живёт дольше основной операции. Если приложение запускает дочерние команды, проверьте наследование файловых дескрипторов: доступ к открытому объекту может сохраняться у ребёнка, даже когда родитель больше им не пользуется.

Увидеть объект у обоих процессов

ps -p 1234,5678 -o pid,ppid,cmd
ls -l /proc/1234/fd
ls -l /proc/5678/fd
cat /proc/5678/fdinfo/7

1234 и 5678 — PID родителя и ребёнка; 7 — номер подозрительного дескриптора у ребёнка. Номер локален для процесса. Одинаковая цифра 7 в двух программах ничего не доказывает; сравнивайте объект, время запуска и поведение. У нескольких дескрипторов может быть общее описание открытого файла, включая текущую позицию.

После fork ребёнок получает копии открытых дескрипторов. При exec они сохраняются, кроме отмеченных close-on-exec. Поэтому короткая команда, запускаемая приложением, способна случайно получить доступ к файлу, который ей не нужен. Механизм флага O_CLOEXEC описан в open(2).

Различить передачу и повторное открытие

Если ребёнок сам открывает тот же путь по своей конфигурации, close-on-exec не изменит ситуацию. Сопоставьте момент появления дескриптора с запуском, аргументы команды и её документацию. Для воспроизведения на стенде полезен небольшой родитель, который открывает учебный файл и запускает безопасную дочернюю команду с явной политикой наследования.

Не экспериментируйте с закрытием произвольного fd через отладчик в рабочем процессе. Приложение может считать его сокетом, файлом журнала или каналом связи; номер способен быстро использоваться повторно. Исправлять нужно место открытия или API запуска, а не чужую таблицу дескрипторов во время работы.

Установить явное правило в коде

В C для файлов, которые не должны переходить через exec, используйте O_CLOEXEC при самом open. Установка флага отдельным последующим вызовом оставляет окно между открытием и изменением флага, существенное в многопоточной программе с параллельным запуском детей. У других API есть собственные атомарные варианты и настройки запуска subprocess.

Явно перечислите дескрипторы, которые действительно нужны ребёнку: стандартный ввод, вывод, ошибки и специальные каналы протокола. Некоторые службы намеренно передают открытые объекты. Массовое закрытие всего унаследованного нарушит такой протокол, поэтому проверяйте контракт запуска конкретного приложения.

Сверить признаки общего открытия

В учебной ситуации родитель PID 7000 открыл /srv/tmp/export.part, затем запустил помощника PID 7010. У помощника в fd виден тот же файл под номером 9, хотя аргументы команды его не содержат. После закрытия файла родителем запись у помощника остаётся. Это достаточный повод проверить контракт наследования и точку запуска; одно совпадение пути ещё не доказывает общий open file description.

Чтобы отличить наследование от независимого открытия, на стенде измените политику передачи fd и повторите запуск. Если лишний объект исчезает только при close-on-exec, гипотеза подтверждается. Если он появляется снова после exec, исследуйте действия помощника: возможно, файл открывается по конфигурации. Так изменение проверяет механизм, а не просто очищает симптом.

Проверить устранение утечки

На стенде повторите тот же запуск и осмотрите /proc дочернего процесса. Учебный файл должен исчезнуть из его fd, нужные каналы — сохраниться, команда — завершиться с ожидаемым результатом. Затем повторите сценарий закрытия файла или блокировки, ради которого началось расследование.

В курсе Linux и Bash для DevOps-инженеров управление процессами и файловой системой даёт основу для такого анализа. Откройте программу, чтобы увереннее сопоставлять дерево процессов, открытые ресурсы и жизненный цикл службы.

Ещё по теме

Продолжить на практике

  • Linux и Bash для DevOps-инженеров ↗

    Linux для работы с серверами: процессы, права, systemd, сеть, диски и логи. Автоматизируйте повседневные задачи на Bash и разберите типичные сбои сервиса — от занятого порта до переполненного диска.