Программирование в стандарте POSIX

Утилиты и функции, обслуживающие понятие пользователь

Разбить на страницы
Показывать лекцию целиком

Данные, ассоциированные с пользователем

Операционная система, соответствующая стандарту POSIX, должна поддерживать базу данных пользователей, в которой о каждом из них хранится по крайней мере следующая информация:

  • имя пользователя ;
  • числовой идентификатор пользователя ;
  • числовой идентификатор начальной группы ;
  • начальный рабочий каталог ;
  • начальная программа пользователя.
  • Поясним смысл перечисленных элементов данных.

    Каждый зарегистрированный пользователь ОС имеет имя, которое он указывает для целей идентификации при входе в систему. После проведения идентификации и, как правило, аутентификации пользователя, с ним ассоциируются (неотрицательные) числовые идентификаторы пользователя и начальной группы. В отличие от имен, ОС оперирует ими во всех случаях, кроме первоначальной идентификации. Затем запускается начальная программа пользователя (например, командный интерпретатор shell) с указанным начальным рабочим каталогом.

    Поля начального рабочего каталога и начальной программы пользователя могут быть пустыми; в таком случае их трактовка зависит от реализации. Обычно в системе определена подразумеваемая начальная программа, в качестве которой обычно используется /bin/sh.

    Пользователи объединяются в группы ; каждый пользователь является членом хотя бы одной из них. Для групп также существует база данных, ее записи содержат по крайней мере следующие поля:

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

    С объектно-ориентированной точки зрения можно считать, что класс "пользователь" предоставляет один метод - начальную программу. Его можно применить для программирования определенных услуг. Например, если нужно дать возможность любому человеку (не обязательно зарегистрированному пользователю), оказавшемуся рядом со свободным терминалом, узнать текущие дату и время, заводят пользователя date с начальной программой /bin/date.

    Опросить ассоциированные с пользователем данные (точнее, идентификаторы и имена пользователя и начальной группы, а также всех групп, членами которых ему разрешено быть, - так называемых дополнительных групп ) позволяет служебная программа

    id  [имя_пользователя]

    Если имя_пользователя опущено, выдается информация о текущем пользователе и его текущей группе.

    Пример использования служебной программы id. Команда

    id root

    может выдать на стандартный вывод следующий результат:

    uid=0(root) gid=0(root) groups=0(root),
    1(bin),2(daemon),3(sys),4(adm),6(disk),
    10(wheel)

    Числовой идентификатор 0 характеризует суперпользователя, который в большинстве Unix-систем не подвержен контролю прав доступа.

    Входное имя текущего пользователя можно узнать также с помощью утилиты

    logname

    и функции getlogin():

    #include <unistd.h>
    char *getlogin (void);

    Отметим, что одному идентификатору пользователя может соответствовать несколько записей в базе данных пользователей, различающихся входными именами и, возможно, другими атрибутами; logname и getlogin() выдают имя, под которым начат текущий сеанс работы с ОС.

    Над базой данных пользователей определены операции поиска по идентификатору или имени пользователя, реализуемые, соответственно, функциями getpwuid() и getpwnam() (см. листинг 3.1):

    #include <pwd.h>
    struct passwd *getpwuid (uid_t uid);
    #include <pwd.h>
    struct passwd *getpwnam (const char *name);

    По стандарту структура passwd должна содержать по крайней мере следующие поля, соответствующие описанным выше обязательным элементам базы данных пользователей:

    char  *pw_name;  /* Имя пользователя */
    
    uid_t  pw_uid;   /* Числовой идентификатор
                        пользователя */
    gid_t  pw_gid;   /* Числовой идентификатор 
                           начальной группы */
    char  *pw_dir;   /* Начальный рабочий 
                           каталог */
    char  *pw_shell; /* Начальная программа 
                           пользователя */

    Типы uid_t и gid_t определяются в заголовочном файле <sys/types.h>.

    Приведем пример выдачи информации о текущем пользователе и пользователе root с идентификатором 0 (см. листинг 3.2).

    #include <unistd.h>
    #include <sys/types.h>
    #include <pwd.h>
    #include <stdio.h>
    /* Печать элемента базы данных пользователей */
    static void print_pwent (const struct passwd *pw) {
     printf ("Имя пользователя: %s\n", pw->pw_name);
     printf ("Идентификатор пользователя: %d\n", pw->pw_uid);
     printf ("Идентификатор группы: %d\n", pw->pw_gid);
     printf ("Начальный каталог: %s\n", pw->pw_dir);
     printf ("Начальная программа: %s\n", pw->pw_shell);
    }
    int main (void) {
     char *lgnm;           /* Имя текущего пользователя */
     struct passwd *pw;    /* Данные о текущем пользователе */
     /* Поиск и печать информации о текущем пользователе */
     if ((lgnm = getlogin ()) == NULL || (pw = getpwnam (lgnm)) == NULL) {
       fprintf (stderr, "\nНе удалось найти информацию о текущем пользователе\n");
       return 1;
     }
     printf ("\nИнформация о текущем пользователе\n");
     print_pwent (pw);
     /* То же для пользователя root */
     if ((pw = getpwuid ((uid_t) 0)) == NULL) {
       fprintf (stderr, "\nНе удалось найти информацию о пользователе root\n");
       return 1;
     }
     printf ("\nИнформация о пользователе root\n");
     print_pwent (pw);
     return 0;
    }

    По окончании работы этой программы может быть получен следующий результат (см. листинг 3.3):

    Информация о текущем пользователе
    Имя пользователя: galat
    Идентификатор пользователя: 108
    Идентификатор группы: 3
    Начальный каталог: /home/galat
    Начальная программа: /bin/sh
    Информация о пользователе root
    Имя пользователя: root
    Идентификатор пользователя: 0
    Идентификатор группы: 0
    Начальный каталог: /root
    Начальная программа: /bin/bash

    Аналогичные функции имеются для поиска в базе данных групп - getgrgid() и getgrnam() (см. листинг 3.4):

    #include <grp.h>
    struct group *getgrgid (gid_t gid);
    #include <grp.h>
    struct group *getgrnam (const char *name);

    Структура group обязана содержать поля

    char   *gr_name;/* Имя группы */
    gid_t   gr_gid; /* Числовой идентификатор 
                       группы */
    char  **gr_mem; /* Указатель на ограниченный 
                       пустым указателем массив
                       символьных указателей на 
                       имена пользователей,
                       которым разрешено 
                       становиться членами 
                       данной группы */

    Применение функции getgrgid() отражено в листинге 3.5.

    #include <sys/types.h>
    #include <grp.h>
    #include <stdio.h>
    /* Печать списка пользователей, включенных в группу с заданным идентификатором */
    static int print_gr_mem (const gid_t gid) {
     struct group *grp;    /* Данные о группе */
     char **c_gr_mem;      /* Текущий указатель на имя члена группы */
     char *c_gr_mem_name;  /* Текущее имя члена группы */
     if ((grp = getgrgid (gid)) == NULL) {
       fprintf (stderr, "\nНе удалось найти информацию о группе с идентификатором %d\n", gid);
       return 1;
     }
     printf ("\nПользователи, включенные в группу с идентификатором %d:\n", gid);
     c_gr_mem = grp->gr_mem;
     while ((c_gr_mem_name = *c_gr_mem++) != NULL) {
       printf(" %-8.8s", c_gr_mem_name);
     }
     printf ("\n");
     return 0;
    }
    int main (void) {
     return print_gr_mem (1);
    }

    Приведенная в качестве примера программа может привести к результату, показанному в листинге 3.6:

    Пользователи, включенные в группу 
    с идентификатором 1:
    root     bin      daemon

    Для смены текущей группы пользователя предназначена служебная программа newgrp (стандарт POSIX-2001 относит ее к числу необязательных, входящих в расширение "Мобильность пользователей", UP):

    newgrp  [-l] [группа]

    Группа, в которую осуществляется переход, задается именем или числовым идентификатором. Будучи вызванной без аргументов, утилита newgrp возвращает пользователя в его начальную группу, заданную в базе данных пользователей.

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

    Если пользователь не входит в список возможных членов новой группы, при переходе в нее может запрашиваться пароль, однако в стандарте POSIX-2001 этот аспект считается зависящим от реализации. Более того, отмечается, что в базе данных групп нет удобных способов задания паролей, поэтому их использование в приложениях не приветствуется, а со временем пароли групп могут стать ненужными.

    Служебные программы, обслуживающие взаимодействие пользователей

    Активными мы будем называть пользователей, работающих в системе в некоторый момент времени.

    Чтобы узнать, какие пользователи активны и за какими терминалами они работают, можно воспользоваться служебной программой

    who

    (заметим, что стандарт POSIX-2001 трактует ее как необязательную, входящую в расширение "Мобильность пользователей").

    Выдача утилиты who может выглядеть, например, так (правый столбец означает время входа в систему):

    galat    ttyS4    Aug 22 12:41
    kost     ttyS6    Aug 22 10:09

    К той же дополнительной категории, что и who, принадлежат утилиты write, talk и mesg (а также описанная выше служебная программа newgrp ).

    После установления соединения утилита

    write  имя_пользователя [терминал]

    позволяет построчно пересылать стандартный ввод отправителя на терминал пользователя-получателя. Аргументы имя_пользователя и терминал задаются в том виде, как их выводит служебная программа who. Необязательный аргумент [терминал] нужен в тех случаях, когда пользователь-получатель вошел в систему с нескольких терминалов.

    Утилиту

    talk  имя_пользователя [терминал]

    можно рассматривать как более современный аналог write, поскольку она имеет экранный интерфейс и поддерживает двустороннее взаимодействие активных пользователей.

    С помощью служебной программы

    mesg [y|n]

    пользователь может разрешить или запретить установление соединений со своим терминалом. При вызове без аргументов mesg выдает текущий статус терминала.

    Приведем пример употребления описанных служебных программ. Рассылку сообщения-аргумента всем активным пользователям можно реализовать посредством shell-процедуры (см. листинг 3.7).

    who | while read a b c
         do
           write $a $b << !
             $1 !
         done

    Обратим внимание на использование во вставке значения аргумента shell-процедуры.

    Базовым средством обеспечения почтового взаимодействия между пользователями, согласно стандарту POSIX-2001, является служебная программа mailx. Она позволяет и отправлять, и получать письма. В первом случае ее следует вызывать командной строкой

    mailx  [-s тема] адрес ...

    во втором -

    mailx -e

    или

    mailx  [опция ...]

    или

    mailx  -f [опция ...] [почтовый_ящик]

    В процессе отправки текст письма читается со стандартного ввода. При получении всеми указанными выше способами, кроме последнего, проверяется системный почтовый ящик, выделенный текущему пользователю, а при наличии опции -f в качестве почтового ящика используется явно заданный файл. Опция -e предписывает только проверить почтовый ящик и, если он не пуст, вернуть код успеха.

    В режиме получения можно отправлять письма и управлять содержимым почтового ящика (для чего утилита mailx предоставляет весьма богатый набор команд), однако поддержку этого режима стандарт POSIX-2001 относит к числу необязательных возможностей.

    При отправке писем shell-процедурами часто пользуются вставками (см. листинг 3.8):

    address=...
      . . .
    mailx $address << !
      . . .
    текст письма
      . . .
    !

    Разумеется, интерактивные пользователи редко применяют утилиту mailx напрямую; для работы с почтой практически во всех ОС имеются средства с более дружественным интерфейсом.

    Страницы:

    Данные, ассоциированные с пользователем

    Операционная система, соответствующая стандарту POSIX, должна поддерживать базу данных пользователей, в которой о каждом из них хранится по крайней мере следующая информация:

  • имя пользователя ;
  • числовой идентификатор пользователя ;
  • числовой идентификатор начальной группы ;
  • начальный рабочий каталог ;
  • начальная программа пользователя.
  • Поясним смысл перечисленных элементов данных.

    Каждый зарегистрированный пользователь ОС имеет имя, которое он указывает для целей идентификации при входе в систему. После проведения идентификации и, как правило, аутентификации пользователя, с ним ассоциируются (неотрицательные) числовые идентификаторы пользователя и начальной группы. В отличие от имен, ОС оперирует ими во всех случаях, кроме первоначальной идентификации. Затем запускается начальная программа пользователя (например, командный интерпретатор shell) с указанным начальным рабочим каталогом.

    Поля начального рабочего каталога и начальной программы пользователя могут быть пустыми; в таком случае их трактовка зависит от реализации. Обычно в системе определена подразумеваемая начальная программа, в качестве которой обычно используется /bin/sh.

    Пользователи объединяются в группы ; каждый пользователь является членом хотя бы одной из них. Для групп также существует база данных, ее записи содержат по крайней мере следующие поля:

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

    С объектно-ориентированной точки зрения можно считать, что класс "пользователь" предоставляет один метод - начальную программу. Его можно применить для программирования определенных услуг. Например, если нужно дать возможность любому человеку (не обязательно зарегистрированному пользователю), оказавшемуся рядом со свободным терминалом, узнать текущие дату и время, заводят пользователя date с начальной программой /bin/date.

    Опросить ассоциированные с пользователем данные (точнее, идентификаторы и имена пользователя и начальной группы, а также всех групп, членами которых ему разрешено быть, - так называемых дополнительных групп ) позволяет служебная программа

    id  [имя_пользователя]

    Если имя_пользователя опущено, выдается информация о текущем пользователе и его текущей группе.

    Пример использования служебной программы id. Команда

    id root

    может выдать на стандартный вывод следующий результат:

    uid=0(root) gid=0(root) groups=0(root),
    1(bin),2(daemon),3(sys),4(adm),6(disk),
    10(wheel)

    Числовой идентификатор 0 характеризует суперпользователя, который в большинстве Unix-систем не подвержен контролю прав доступа.

    Входное имя текущего пользователя можно узнать также с помощью утилиты

    logname

    и функции getlogin():

    #include <unistd.h>
    char *getlogin (void);

    Отметим, что одному идентификатору пользователя может соответствовать несколько записей в базе данных пользователей, различающихся входными именами и, возможно, другими атрибутами; logname и getlogin() выдают имя, под которым начат текущий сеанс работы с ОС.

    Над базой данных пользователей определены операции поиска по идентификатору или имени пользователя, реализуемые, соответственно, функциями getpwuid() и getpwnam() (см. листинг 3.1):

    #include <pwd.h>
    struct passwd *getpwuid (uid_t uid);
    #include <pwd.h>
    struct passwd *getpwnam (const char *name);

    По стандарту структура passwd должна содержать по крайней мере следующие поля, соответствующие описанным выше обязательным элементам базы данных пользователей:

    char  *pw_name;  /* Имя пользователя */
    
    uid_t  pw_uid;   /* Числовой идентификатор
                        пользователя */
    gid_t  pw_gid;   /* Числовой идентификатор 
                           начальной группы */
    char  *pw_dir;   /* Начальный рабочий 
                           каталог */
    char  *pw_shell; /* Начальная программа 
                           пользователя */

    Типы uid_t и gid_t определяются в заголовочном файле <sys/types.h>.

    Приведем пример выдачи информации о текущем пользователе и пользователе root с идентификатором 0 (см. листинг 3.2).

    #include <unistd.h>
    #include <sys/types.h>
    #include <pwd.h>
    #include <stdio.h>
    /* Печать элемента базы данных пользователей */
    static void print_pwent (const struct passwd *pw) {
     printf ("Имя пользователя: %s\n", pw->pw_name);
     printf ("Идентификатор пользователя: %d\n", pw->pw_uid);
     printf ("Идентификатор группы: %d\n", pw->pw_gid);
     printf ("Начальный каталог: %s\n", pw->pw_dir);
     printf ("Начальная программа: %s\n", pw->pw_shell);
    }
    int main (void) {
     char *lgnm;           /* Имя текущего пользователя */
     struct passwd *pw;    /* Данные о текущем пользователе */
     /* Поиск и печать информации о текущем пользователе */
     if ((lgnm = getlogin ()) == NULL || (pw = getpwnam (lgnm)) == NULL) {
       fprintf (stderr, "\nНе удалось найти информацию о текущем пользователе\n");
       return 1;
     }
     printf ("\nИнформация о текущем пользователе\n");
     print_pwent (pw);
     /* То же для пользователя root */
     if ((pw = getpwuid ((uid_t) 0)) == NULL) {
       fprintf (stderr, "\nНе удалось найти информацию о пользователе root\n");
       return 1;
     }
     printf ("\nИнформация о пользователе root\n");
     print_pwent (pw);
     return 0;
    }

    По окончании работы этой программы может быть получен следующий результат (см. листинг 3.3):

    Информация о текущем пользователе
    Имя пользователя: galat
    Идентификатор пользователя: 108
    Идентификатор группы: 3
    Начальный каталог: /home/galat
    Начальная программа: /bin/sh
    Информация о пользователе root
    Имя пользователя: root
    Идентификатор пользователя: 0
    Идентификатор группы: 0
    Начальный каталог: /root
    Начальная программа: /bin/bash

    Аналогичные функции имеются для поиска в базе данных групп - getgrgid() и getgrnam() (см. листинг 3.4):

    #include <grp.h>
    struct group *getgrgid (gid_t gid);
    #include <grp.h>
    struct group *getgrnam (const char *name);

    Структура group обязана содержать поля

    char   *gr_name;/* Имя группы */
    gid_t   gr_gid; /* Числовой идентификатор 
                       группы */
    char  **gr_mem; /* Указатель на ограниченный 
                       пустым указателем массив
                       символьных указателей на 
                       имена пользователей,
                       которым разрешено 
                       становиться членами 
                       данной группы */

    Применение функции getgrgid() отражено в листинге 3.5.

    #include <sys/types.h>
    #include <grp.h>
    #include <stdio.h>
    /* Печать списка пользователей, включенных в группу с заданным идентификатором */
    static int print_gr_mem (const gid_t gid) {
     struct group *grp;    /* Данные о группе */
     char **c_gr_mem;      /* Текущий указатель на имя члена группы */
     char *c_gr_mem_name;  /* Текущее имя члена группы */
     if ((grp = getgrgid (gid)) == NULL) {
       fprintf (stderr, "\nНе удалось найти информацию о группе с идентификатором %d\n", gid);
       return 1;
     }
     printf ("\nПользователи, включенные в группу с идентификатором %d:\n", gid);
     c_gr_mem = grp->gr_mem;
     while ((c_gr_mem_name = *c_gr_mem++) != NULL) {
       printf(" %-8.8s", c_gr_mem_name);
     }
     printf ("\n");
     return 0;
    }
    int main (void) {
     return print_gr_mem (1);
    }

    Приведенная в качестве примера программа может привести к результату, показанному в листинге 3.6:

    Пользователи, включенные в группу 
    с идентификатором 1:
    root     bin      daemon

    Для смены текущей группы пользователя предназначена служебная программа newgrp (стандарт POSIX-2001 относит ее к числу необязательных, входящих в расширение "Мобильность пользователей", UP):

    newgrp  [-l] [группа]

    Группа, в которую осуществляется переход, задается именем или числовым идентификатором. Будучи вызванной без аргументов, утилита newgrp возвращает пользователя в его начальную группу, заданную в базе данных пользователей.

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

    Если пользователь не входит в список возможных членов новой группы, при переходе в нее может запрашиваться пароль, однако в стандарте POSIX-2001 этот аспект считается зависящим от реализации. Более того, отмечается, что в базе данных групп нет удобных способов задания паролей, поэтому их использование в приложениях не приветствуется, а со временем пароли групп могут стать ненужными.

    Служебные программы, обслуживающие взаимодействие пользователей

    Активными мы будем называть пользователей, работающих в системе в некоторый момент времени.

    Чтобы узнать, какие пользователи активны и за какими терминалами они работают, можно воспользоваться служебной программой

    who

    (заметим, что стандарт POSIX-2001 трактует ее как необязательную, входящую в расширение "Мобильность пользователей").

    Выдача утилиты who может выглядеть, например, так (правый столбец означает время входа в систему):

    galat    ttyS4    Aug 22 12:41
    kost     ttyS6    Aug 22 10:09

    К той же дополнительной категории, что и who, принадлежат утилиты write, talk и mesg (а также описанная выше служебная программа newgrp ).

    После установления соединения утилита

    write  имя_пользователя [терминал]

    позволяет построчно пересылать стандартный ввод отправителя на терминал пользователя-получателя. Аргументы имя_пользователя и терминал задаются в том виде, как их выводит служебная программа who. Необязательный аргумент [терминал] нужен в тех случаях, когда пользователь-получатель вошел в систему с нескольких терминалов.

    Утилиту

    talk  имя_пользователя [терминал]

    можно рассматривать как более современный аналог write, поскольку она имеет экранный интерфейс и поддерживает двустороннее взаимодействие активных пользователей.

    С помощью служебной программы

    mesg [y|n]

    пользователь может разрешить или запретить установление соединений со своим терминалом. При вызове без аргументов mesg выдает текущий статус терминала.

    Приведем пример употребления описанных служебных программ. Рассылку сообщения-аргумента всем активным пользователям можно реализовать посредством shell-процедуры (см. листинг 3.7).

    who | while read a b c
         do
           write $a $b << !
             $1 !
         done

    Обратим внимание на использование во вставке значения аргумента shell-процедуры.

    Базовым средством обеспечения почтового взаимодействия между пользователями, согласно стандарту POSIX-2001, является служебная программа mailx. Она позволяет и отправлять, и получать письма. В первом случае ее следует вызывать командной строкой

    mailx  [-s тема] адрес ...

    во втором -

    mailx -e

    или

    mailx  [опция ...]

    или

    mailx  -f [опция ...] [почтовый_ящик]

    В процессе отправки текст письма читается со стандартного ввода. При получении всеми указанными выше способами, кроме последнего, проверяется системный почтовый ящик, выделенный текущему пользователю, а при наличии опции -f в качестве почтового ящика используется явно заданный файл. Опция -e предписывает только проверить почтовый ящик и, если он не пуст, вернуть код успеха.

    В режиме получения можно отправлять письма и управлять содержимым почтового ящика (для чего утилита mailx предоставляет весьма богатый набор команд), однако поддержку этого режима стандарт POSIX-2001 относит к числу необязательных возможностей.

    При отправке писем shell-процедурами часто пользуются вставками (см. листинг 3.8):

    address=...
      . . .
    mailx $address << !
      . . .
    текст письма
      . . .
    !

    Разумеется, интерактивные пользователи редко применяют утилиту mailx напрямую; для работы с почтой практически во всех ОС имеются средства с более дружественным интерфейсом.

    Вернуться к учебному плану