Книга: Разработка приложений в среде Linux. Второе издание

16.1.5. Запись вручную

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.

Если вы не хотите применять расширения, мы рекомендуем использовать интерфейс utmpx, поскольку он наиболее переносим, пока вы не используете расширения, и строго стандартизован.

Однако если вы хотите применять расширения, мы рекомендуем использовать интерфейс utmp, поскольку glibc предоставляет определения, позволяющие записать переносимый код, пользующийся преимуществами расширений.

Существует также смешанный подход — включите оба заголовочных файла и используйте определения, предоставляемые glibc для интерфейса utmp, чтобы решить, применять ли расширения в интерфейсе utmpx. Этого мы не рекомендуем, поскольку нет гарантии, что заголовочные файлы utmp.h и utmpx.h не будут конфликтовать с системами, не относящимися к Linux. Если ожидается максимальная переносимость и функциональность, в одной из этих областей придется записать некоторые коды дважды — первую версию с использованием utmpx для легкого переноса в новые системы, а вторую с применением #ifdef — для максимальной функциональности в каждой новой системе, в которую вы перемещаетесь.

Здесь документируются лишь наиболее распространенные расширения; документация glibc покрывает все поддерживаемые расширения. Функции utmp работают в терминах struct utmp; мы игнорируем некоторые расширения. Структура и функции utmpx работают точно так же, как структура и функции utmp, поэтому мы не документируем их отдельно. Обратите внимание, что такая же структура используется и для utmp, и для wtmp, поскольку обе базы данных очень похожи.

struct utmp {
 short int ut_type;          /* тип входа */
 pid_t ut_pid;               /* идентификатор процесса входа */
 char ut_line[UT_LINESIZE];  /* 32 символа */
 char ut_id[4];              /* идентификатор inittab */
 char ut_user[UT_NAMESIZE];  /* 32 символа */
 char ut_host[UT_HOSTSIZE];  /* 256 символов */
 struct timeval ut_tv;
 struct exit_status ut_exit; /* состояние бездействующего процесса */
 long ut_session;
 int32_t ut_addr_v6[4];
};

Многие одинаковые элементы являются частью struct utmpx под тем же именем. Элементы, от которых не требуется быть элементами struct utmpx, комментируются как "не стандартизованные POSIX" (ни один из них не стандартизован как часть struct utmp, поскольку сама struct utmp не стандартизована).

Элементы массива символов необязательно являются строками, завершающимися NULL. Используйте sizeof() либо другие ограничения размеров благоразумно.

ut_type Одно из следующих значений: EMPTY, INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, DEAD_PROCESS, BOOT_TIME, NEW_TIME, OLD_TIME, RUN_LVL или ACCOUNTING, каждое из которых описано ниже.
ut_tv Время, ассоциированное с событием. Это единственный элемент, кроме ut_type, определяемый POSIX как всегда подходящий для непустых элементов. В некоторых системах вместо этого есть элемент ut_time, измеряемый только в секундах.
ut_pid Идентификатор ассоциированного процесса для всех типов, заканчивающихся на _PROCESS.
ut_id Идентификатор inittab ассоциированного процесса, для всех типов, заканчивающихся на _PROCESS. Это первое поле в незакомментированных строках файла /etc/inittab, где поля разделены символами :. Сетевые регистрации, не ассоциированные с inittab, могут использовать это по-другому; например, могут включать части информации об устройстве.4
ut_line Строка (базовое имя устройства или номер локального дисплея для X), ассоциированная с процессом. Спецификация POSIX о состоянии ut_line не ясна; она не считает ut_line значащей для LOGIN_PROCESS, но с другой стороны предполагает, что она значащая для LOGIN_PROCESS, и это подтверждается на практике. POSIX утверждает, что ut_line значащая для USER_PROCESS. На практике она также часто значащая для DEAD_PROCESS, в зависимости от происхождения бездействующего процесса.
ut_user Обычно это имя зарегистрированного пользователя; это также может быть имя зарегистрированного процесса (обычно LOGIN) в зависимости от значения ut_type.
ut_host Имя удаленного хоста, вошедшего в систему или иным образом ассоциированного с этим процессом. Элемент ut_host относится только к USER_PROCESS. Этот элемент не стандартизован POSIX.
ut_exit ut_exit.e_exit дает код завершения, что предоставляется макросом WEXITSTATUS(), a ut_exit.e_termination дает сигнал, вызвавший завершение процесса (если он был завершен сигналом), что предоставляется макросом WTERMSIG(). Этот элемент не стандартизован POSIX.
ut_session Идентификатор сеанса в системе X Window. Этот элемент не стандартизован POSIX.
ut_addr_v6 IP-адрес удаленного хоста в случае активизации USER_PROCESS подключением с удаленного хоста. Используйте функцию inet_ntop() для генерирования печатного содержания. Если первая группа не равна нулю, тогда это адрес IPV4 (inet_ntop() принимает аргумент AF_INET); в противном случае это адрес IPV6 (inet_ntop() принимает аргумент AF_INET6). Этот элемент не стандартизован POSIX.

Элемент ut_type устанавливает, каким образом определяются остальные элементы. Некоторые величины ut_type зарезервированы для записи системной информации; они полезны только для специализированных системных программ и документируются не полностью.

EMPTY В данной записи utmp нет достоверных данных (такие записи позже можно повторно использовать), поэтому игнорируйте ее содержимое. Другие элементы структуры являются незначащими.
INIT_PROCESS Приведенный процесс был порожден непосредственно инициализацией. Это значение могут устанавливать системные программы (обычно только сам процесс инициализации); приложения должны прочитывать и распознавать это значение, но не должны ее устанавливать. Значащими являются элементы ut_pid, ut_id и ut_tv.
LOGIN_PROCESS Экземпляры регистрационной программы, ожидающие регистрации пользователя. Элементы ut_id, ut_pid и ut_tv полезны; элемент ut_user полезен номинально (в Linux он сообщает LOGIN, но это имя процесса регистрации определяется реализацией в соответствии с POSIX).
USER_PROCESS Этот элемент определяет лидера сеанса для зарегистрированного пользователя. Это может быть регистрационная программа после регистрации пользователя, управляющая программа монитора либо сеанса для входа в X Window System, программа эмуляции терминала, сконфигурированная для пометки сеансов регистрации, или любая интерактивная регистрация пользователя. Значащими являются элементы ut_id, ut_user, ut_line, ut_pid и ut_tv.
DEAD_PROCESS Приведенный процесс был лидером сеанса для зарегистрированного пользователя, но завершился. Значащими являются элементы ut_id, ut_pid и ut_tv в соответствии POSIX. Элемент ut_exit (не установленный POSIX) значащий только в данном контексте.
BOOT_TIME Время начальной загрузки системы. В utmp это самая поздняя загрузка; в wtmp это элемент для каждой загрузки системы со времени очистки wtmp. Значащим является только ut_tv.
OLD_TIME и NEW_TIME Используются только для записи "скачков" времени. Записываются парами. Не рекомендуется зависеть от записи этих элементов в систему, даже если время на часах по какой-либо причине изменилось.
RUN_LVL и ACCOUNTING Внутренние системные величины; в приложениях использовать не следует.

Ниже приведены интерфейсы, определяемые XPG2, SVID 2 и FSSTND 1.2.

#include <utmp.h>
int utmpname(char * file);
struct utmp *getutent(void);
struct utmp *getutid(const struct utmp * id);
struct utmp *getutline(const struct utmp * line);
struct utmp *pututline(const struct utmp * ut);
void setutent(void);
void endutent(void);
void updwtmp(const char * file, const struct utmp * ut);
void logwtmp(const char * line, const char * name, const char * host);

Каждая запись в базе данных utmp или wtmp называется строкой. Все функции, возвращающие указатель на struct utmp, возвращают его на статические данные в случае успеха и NULL — в случае ошибки. Обратите внимание, что статические данные переписаны каждым новым вызовом на каждую функцию, возвращающую struct utmp. Также стандарт POSIX (для utmpx) требует очистки статических данных приложением перед началом какого-либо поиска.

Версии utmpx этих функций принимают struct utmpx вместо struct utmp, требуют включения utmpx.h и называются getutxent, getutxid, getutxline, pututxline, setutxent и endutxent, но в другом случае они идентичны версиям utmp этих функций в Linux. Функции utmpxname(), определенной POSIX, не существует, хотя некоторые платформы могут определять ее в любом случае (например, glibc).

Функция utmpname() используется для определения просматриваемой базы данных. Базой данных по умолчанию является utmp, но вместо этого функцию можно применять для указания на wtmp. Два предопределенных имени — это _PATH_UTMP для файла utmp и _PATH_WTMP для файла wtmp; для целей тестирования можно выбрать указатель на локальную копию. Функция utmpname() возвращает ноль в случае успеха и ненулевое значение в случае ошибки. Но успех может означать просто то, что имя файла удалось скопировать в библиотеку; это не означает, что база данных действительно существует в пути, предоставленном для нее.

Функция getutent() просто возвращает следующую строку из базы данных. Если база данных еще не открыта, она возвращает содержимое первой строки. Если строки больше не доступны, она возвращает NULL.

Функция getutid() принимает struct utmp и рассматривает лишь один или два элемента. Если ut_type является BOOT_TIME, OLD_TIME или NEW_TIME, она возвращает следующую строку этого типа. Если ut_type является INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS или DEAD_PROCESS, тогда getutid() возвращает следующую строку, которая соответствует любому из типов, также имеющему значение ut_id, которое соответствует значению ut_id в struct utmp, передаваемой getutid(). Перед повторным вызовом потребуется удалить из struct utmp данные, возвращаемые getutid(); POSIX разрешает возвращать ту же строку, что и предыдущий вызов. Если соответствующих строк нет, возвращается NULL.

Функция getutline() возвращает следующую строку с ut_id, установленным в LOGIN_PROCESS или USER_PROCESS. Эти процессы тоже имеют значение ut_line, соответствующее значению ut_line в struct utmp, которая передается в getutline(). Как и в случае getutid(), необходимо удалить данные, возвращаемые getutline(), из struct utmp перед его повторным вызовом; в противном случае POSIX разрешает возвращать ту же строку, что и предыдущий вызов. Если соответствующих строк нет, возвращается NULL.

Функция pututline() модифицирует (или по необходимости добавляет) запись базы данных, соответствующую элементу ut_line аргумента struct utmp. Она делает это только в том случае, если у процесса есть полномочия на модификацию базы данных. Если модификация базы данных прошла успешно, она возвращает struct utmp, который соответствует данным, записанным в базу. В ином случае возвращается NULL. Функция pututline() не применима к базе данных wtmp. Для модификации базы данных wtmp используйте updwtmp() или logwtmp().

Функция setutent() перемещает внутренний указатель базы данных в начало.

Функция endutent() закрывает базу данных. Это закрывает файловый дескриптор и освобождает ассоциированные данные. Вызывайте endutent() как перед использованием utmpname() для доступа к другому файлу utmp, так и после завершения доступа к данным utmp.

Наиболее надежным способом модификации базы данных wtmp являются две функции, определенные BSD и доступные как часть glibc.

Функция updwtmp() принимает файловое имя базы данных wtmp (обычно _PATH_WTMP) и заполненную структуру struct utmp, пытаясь добавить элемент к файлу wtmp. Эта функция не сообщает об ошибках.

Функция logwtmp() является удобной функцией, заполняющей struct utmp и вызывающей updwtmp() для нее. Аргумент line копируется в ut_line, name — в ut_user, host — в ut_host, ut_tv заполняется текущим показанием времени, a ut_pid — текущим идентификатором процесса. Как и updwtmp(), эта функция не сообщает об ошибках.

В программе utmp демонстрируются некоторые методы чтения баз данных utmp и wtmp.

  1: /* utmp.с */
  2:
  3: #include <stdio.h>
  4: #include <unistd.h>
  5: #include <string.h>
  6: #include <time.h>
  7: #include <sys/time.h>
  8: #include <sys/types.h>
  9: #include <sys/socket.h>
 10: #include <netinet/in.h>
 11: #include <arpa/inet.h>
 12: #include <utmp.h>
 13: #include <popt.h>
 14:
 15: void print_utmp_entry(struct utmp * u) {
 16:  struct tm *tp;
 17:  char * type;
 18:  char addrtext[INET6_ADDRSTRLEN];
 19:
 20:  switch (u->ut_type) {
 21:  case EMPTY: type = "EMPTY"; break;
 22:  case RUN_LVL: type = "RUN_LVL"; break;
 23:  case BOOT_TIME: type = "BOOT_TIME"; break;
 24:  case NEW_TIME: type = "NEW_TIME"; break;
 25:  case OLD_TIME: type = "OLD_TIME"; break;
 26:  case INIT_PROCESS: type = "INIT_PROCESS"; break;
 27:  case LOGIN_PROCESS: type = "LOGIN_PROCESS"; break;
 28:  case USER_PROCESS: type = "USER_PROCESS"; break;
 29:  case DEAD_PROCESS: type = "DEAD_PROCESS"; break;
 30:  case ACCOUNTING: type = "ACCOUNTING "; break;
 31:  }
 32:  printf("%-13s:", type);
 33:  switch (u->ut_type) {
 34:  case LOGIN_PROCESS:
 35:  case USER_PROCESS:
 36:  case DEAD_PROCESS:
 37:   printf(" line: %s", u->ut_line);
 38:   /* fall through */
 39:  case INIT_PROCESS:
 40:   printf("n pid: %6d id: %4.4s", u->ut_pid, u->ut_id);
 41:  }
 42:  printf ("n");
 43:  tp = gmtime(&u->ut_tv.tv_sec);
 44:  printf("time: %24.24s.%lun", asctime(tp), u->ut_tv.tv_usec);
 45:  switch (u->ut_type) {
 46:  case USER_PROCESS:
 47:  case LOGIN_PROCESS:
 48:  case RUN_LVL:
 49:  case BOOT_TIME:
 50:   printf("пользователь: %sn", u->ut_user);
 51:  }
 52:  if (u->ut_type == USER_PROCESS) {
 53:   if (u->ut_session)
 54:    printf(" сеанс: %lun", u->ut_session);
 55:   if (u->ut_host)
 56:    printf (" хост: %sn", u->ut_host);
 57:   if (u->ut_addr_v6[0]) {
 58:    if (!(u->ut_addr_v6[1] |
 59:     u->ut_addr_v6[2] |
 60:     u->ut_addr_v6[3])) {
 61:     /* заполнение только первой группы означает адрес IPV4 */
 62:     inet_ntop(AF_INET, u->ut_addr_v6,
 63:     addrtext, sizeof(addrtext));
 64:     printf(" IPV4: %sn", addrtext);
 65:    } else {
 66:     inet_ntop(AF_INET_6, u->ut_addr_v6,
 67:      addrtext, sizeof(addrtext));
 68:     printf (" IPV6: %sn", addrtext);
 69:    }
 70:   }
 71:  }
 72:  if (u->ut_type == DEAD_PROCESS) {
 73:   printf(" завершение : %u: %un",
 74:    u->ut_exit.e_termination,
 75:    u->ut_exit.e_exit);
 76:  }
 77:  printf("n");
 78: }
 79:
 80: struct utmp * get_next_line (char * id, char * line) {
 81:  struct utmp request;
 82:
 83:  if (!id && !line)
 84:   return getutent();
 85:
 86:  memset(&request, 0, sizeof(request));
 87:
 88:  if (line) {
 89:   strncpy(&request.ut_line[0], line, UT_LINESIZE);
 90:   return getutline(&request);
 91:  }
 92:
 93:  request.ut_type = INIT_PROCESS;
 94:  strncpy(&request.ut_id[0], id, 4);
 95:  return getutid(&request);
 96: }
 97:
 98: void print_file(char * name, char * id, char * line) {
 99:  struct utmp * u;
100:
101:  if (utmpname(name)) {
102:   fprintf (stderr, "сбой при открытии базы данных utmp %sn", name);
103:   return;
104:  }
105:  setutent();
106:  printf("%s:n====================n", name);
107:  while ((u = get_next_line(id, line))) {
108:   print_utmp_entry(u);
109:   /* POSIX требует очистки статических данных перед
110:    * повторным вызовом getutline или getutid
111:    */
112:   memset(u, 0, sizeof(struct utmp));
113:  }
114:  endutent();
115: }
116:
117: int main(int argc, const char **argv) {
118:  char * id = NULL, *line = NULL;
119:  int show_utmp = 1, show_wtmp = 0;
120:  int c;
121:  poptContext optCon;
122:  struct poptOption optionsTable[] = {
123:   {"utmp", 'u', POPT_ARG_NONE|POPT_ARGFLAG_XOR,
124:    &show_utmp, 0,
125:    "переключить просмотр содержимого файла utmp", NULL},
126:   { "wtmp", 'w', POPT_ARG_NONE | POPT_ARGFLAG_XOR,
127:     &show_wtmp, 0,
128:     "переключить просмотр содержимого файла wtmp", NULL},
129:   {"id", 'i', POPT_ARG_STRING, &id, 0,
130:    "показать записи процесса для заданного идентификатора inittab",
131:    "<inittab id>" },
132:   {"line", 'l', POPT_ARG_STRING, &line, 0,
133:    "показать записи процесса для заданной строки устройства",
134:    "<line>" },
135:   POPT_AUTOHELP
136:   POPT_TABLEEND
137:  };
138:
139:  optCon = poptGetContext("utmp", argc, argv, optionsTable, 0);
140:  if ((c = poptGetNextOpt(optCon)) < -1) {
141:   fprintf(stderr, "%s:%sn",
142:    poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
143:    poptStrerror(c));
144:   return 1;
145:  }
146:  poptFreeContext(optCon);
147:
148:  if (id && line)
149:   fprintf(stderr, "Невозможно выбирать сразу по идентификатору и строке,"
150:    "выбор по строкеn");
151:
152:  if (show_utmp)
153:   print_file(_PATH_UTMP, id, line);
154:  if (show_utmp && show_wtmp)
155:   printf("nnn");
156:  if (show_wtmp)
157:   print_file(_PATH_WTMP, id, line);
158:
159:  return 0;
160: }

Оглавление книги


Генерация: 1.032. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз