Нормальные процессы не могут менять свои управляющие терминалы; это может делать только лидер сеанса. В Linux изменение управляющего терминала лидера сеанса не распространяется на другие процессы того же сеанса. Лидеры сеансов почти всегда устанавливают управляющий терминал при запуске до создания каких-либо дочерних процессов, чтобы гарантировать, что все процессы сеанса совместно используют один управляющий терминал.
Существуют два интерфейса для смены управляющего tty лидера сеанса. Первый реализуется с помощью нормальных системных вызовов open() и close().
1. Закройте все файловые дескрипторы, относящиеся к текущему управляющему терминалу.
2. Откройте новый терминал без установки флага O_NOCTTY.
Второй метод включает вызовы ioctl() на отдельных файловых дескрипторах, ссылающихся на старые и новые терминалы.
1. Установите флаг TIOCNOTTY на файловый дескриптор, привязанный к исходному управляющему tty (обычно ioctl(0, TIOCNOTTY, NULL) нормально работает). Это разрывает соединение между сеансом и tty.
2. Установите флаг TIOCSCTTY на файловый дескриптор, привязанный к новому управляющему tty. Это устанавливает новый управляющий tty.
Терминал, используемый сеансом, отслеживает то, какая группа процессов считается группой процессов переднего плана. Процессам в этой группе разрешается читать и записывать в терминал, в то время как процессам в другой группе это не разрешено (более подробно о том, что происходит, когда фоновые процессы пытаются читать и производить запись в управляющий терминал, рассказывается в главе 15).
Функция tcsetpgrp() позволяет процессу, работающему на терминале, сменить группу процессов переднего плана для этого терминала[108].
int tcsetpgrp(int ttyfd, pid_t pgrp);
Первый параметр определяет tty, управляющая группа процессов которого изменяется, a pgrp является группой процессов, которую необходимо переместить на передний план. Процессы могут менять группу процессов переднего плана только для своего управляющего терминала. Если процесс, совершающий изменение, не принадлежит к группе процессов переднего плана на этом терминале, генерируется сигнал SIGTTOU, если только он не игнорируется или заблокирован[109].
16.1.3. Принадлежность терминала
Существуют две системные базы данных, используемые для отслеживания зарегистрированных пользователей; utmp применяется для пользователей, зарегистрированных в данный момент, a wtmp является записью всех предыдущих регистраций со времени создания файла. Команда who использует базу данных utmp для отображения списка зарегистрированных пользователей, а команда last — базу данных wtmp для отображения списка пользователей, зарегистрированных в системе после регенерации базы данных wtmp. В системах Linux база данных utmp хранится в файле /var/run/utmp, а база данных wtmp — в файле /var/log/wtmp.
Программы, использующие tty для сеансов регистрации пользователей (независимо от того, ассоциируются ли они с графической регистрацией), должны обновлять эти две системные базы данных, пока пользователь явно не сделает иной запрос; например, некоторые пользователи не хотят, чтобы каждый сеанс оболочки, запускаемый ими в эмуляторе терминала в системе X Window, перечислялся как процесс входа. Добавляйте только интерактивные сеансы, поскольку utmp и wtmp не предназначены для регистрации автоматизированных программ. Любые tty, не являющиеся контролирующими терминалами, обычно в базы данных utmp и wtmp не добавляются.
16.1.4. Запись с помощью utempter
Приложения со встроенными средствами безопасности, использующие pty, имеют недостаточно полномочий для модификации файлов баз данных. Эти приложения должны предоставлять опцию для использования простой вспомогательной программы, доступной в большинстве систем Linux и в некоторых других системах, но не стандартизованной — утилиты utempter. Утилита utempter является setgid (или, при необходимости, setuid) с достаточными полномочиями для модификации баз данных utmp и wtmp. Доступ к ней можно получить через простую библиотеку. Утилита utempter проверяет, владеет ли процесс tty, который пытается войти в базу данных utmp до разрешения операции, utempter предназначена только для pty; другие tty обычно открываются демонами с достаточными полномочиями для модификации файлов системных баз данных.
#include <utempter.h>
void addToUtmp(const char *pty, const char *hostname, int ptyfd);
void removeLineFromUtmp(const char *pty, int ptyfd);
void removeFromUtmp(void);
Функция addToUtmp() принимает три аргумента. Первый, pty, является полным путем к добавляемому pty. Второй, hostname, может быть NULL или сетевым именем системы, из которой сетевое подключение использует этот порожденный pty (что запускает ut_host, рассматриваемый в следующем разделе главы). Третий, ptyfd, должен быть открытым файловым дескриптором, ссылающимся на устройство, названное в аргументе pty.
Функция removeLineFromUtmp() принимает два аргумента; они определяются в точности как аргументы с таким же именем, передаваемые функции addToUtmp().
Некоторые существующие приложения записываются с помощью структуры, усложняющей хранение имени и файлового дескриптора для очистки элемента utmp. Из-за этого библиотека utempter поддерживает кэш самого позднего имени устройства и файлового дескриптора, передаваемого addToUtmp(), и удобную функцию removeFromUtmp(), не принимающую никаких аргументов и действующую как removeLineFromUtmp() на кэшированную информацию. Это подходит только для приложений, добавляющих лишь один элемент utmp; более сложные приложения, использующие более одного pty, должны вместо этого применять removeLineFromUtmp().
16.1.5. Запись вручную
Область обработки utmp и wtmp является одной из тех противоречивых областей, где механизмы различаются между системами и меняются на протяжении лет; даже определение информации, доступной в utmp и wtmp, до сих пор различается между системами. Изначально utmp и wtmp были просто массивами структур, записанных на диск; через некоторое время были созданы программные интерфейсы приложений (API) для надежной обработки записей.
По крайней мере, два таких интерфейса были официально стандартизованы; исходный интерфейс utmp (описанный в XSI, XPG2 и SVID2) и расширенный интерфейс utmpx (описанный в XPG4.2 и в поздних версиях POSIX). В Linux доступны оба интерфейса (utmp и utmpx). Интерфейс utmp, широко варьирующийся между машинами, имеет набор определений, которые делают возможной запись переносимого кода. Этот код пользуется преимуществом расширений, предоставляемых glibc. Более строго стандартизованный интерфейс utmpx в данный момент не предоставляет эти определения, но все еще поддерживает расширения.
Интерфейс Linux utmp был изначально задуман как супермножество других существующих интерфейсов utmp, a utmpx был стандартизован как супермножество других существующих интерфейсов utmp; к счастью, оба набора во многом одинаковы. В Linux различие между структурами данных utmp и utmpx заключается лишь в букве x.
108
Реализации Unix старого типа предоставляли эту функцию с помощью TIOCSPGRP ioctl(), до сих пор поддерживаемого Linux. Для сравнения, tcsetpgrp() можно реализовать как ioctl(ttyfd, TIOCSPGRP, &pgrp).
109
Более подробно о сигналах и их взаимодействии с управлением заданиями рассказывается в главе 12.