Книга: Программирование для Linux. Профессиональный подход

Листинг 11.9. (processes.c) Серверный модуль, отображающий таблицу процессов

Листинг 11.9. (processes.c) Серверный модуль, отображающий таблицу процессов

#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include "server.h"
/* Эта функция записывает в аргументы UID и GID
   идентификаторы пользователя и группы, которым
   принадлежит процесс с указанным идентификатором,
   в случае успешного завершения возвращается нуль,
   иначе -- ненулевое значение. */
static int get_uid_gid(pid_t pid, uid_t* uid, gid_t* gid) {
 char dir_name[64];
 struct stat dir_info;
 int rval;
 /* Формирование имени каталога процесса
    в файловой системе /proc. */
 snprintf(dir_name, sizeof(dir_name), "/proc/%d", (int)pid);
 /* Получение информации о каталоге. */
 rval = stat(dir_name, &dir_info);
 if (rval != 0)
  /* Каталог не найден. Возможно, процесс больше
     не существует. */
  return 1;
 /* Убеждаемся в том, что это действительно каталог. */
 assert(S_ISDIR(dir_info.st_mode));
 /* Определяем интересующие нас идентификаторы. */
 *uid = dir_info.st_uid;
 *gid = dir_info.st_gid;
 return 0;
}
/* Эта функция находит имя пользователя,
   соответствующее заданному идентификатору.
   Возвращаемый буфер должен быть удален
   в вызывающей функции. */
static char* get_user_name(uid_t uid) {
 struct passwd* entry;
 entry = getpwuid(uid);
 if (entry == NULL)
  system_error("getpwuid");
 return xstrdup(entry->pw_name);
}
/* Эта функция находит имя группы, соответствующее
   заданному идентификатору, возвращаемый буфер
   должен быть удален в вызывающей функции. */
static char* get_group_name(gid_t gid) {
 struct group* entry;
 entry = getgrgid(gid);
 if (entry == NULL)
  system_error("getgrgid");
 return xstrdup(entry->gr_name);
}
/* Эта функция находит имя программы, которую выполняет
   процесс с заданным идентификатором. Возвращаемый буфер
   должен быть удален в вызывающей функции. */
static char* get_program_name(pid_t pid) {
 char file_name[64];
 char status_info[256];
 int fd;
 int rval;
 char* open_paren;
 char* close_paren;
 char* result;
 /* Генерируем имя файла "stat", находящегося в каталоге
    данного процесса в файловой системе /proc,
    и открываем этот файл. */
 snprintf(file_name, sizeof(file_name), "/proc/%d/stat",
  (int)pid);
 fd = open(file_name, O_RDONLY);
 if (fd == 1)
  /* Файл не удалось открыть. Возможно, процесс
     больше не существует. */
  return NULL;
 /* Чтение содержимого файла
 rval = read(fd, status_info, sizeof(status_info) — 1);
 close(fd);
 if (rval <= 0)
  /* По какой-то причине файл не удалось прочитать, завершаем
     работу. */
  return NULL;
 /* Завершаем прочитанный текст нулевым символом. */
 status_info[rval] = '';
 /* Имя программы -- это второй элемент файла, заключенный в
    круглые скобки. Находим местоположение скобок. */
 open_paren = strchr(status_info, '(');
 close_paren = strchr(status_info, ')');
 if (open_paren == NULL ||
  close_paren == NULL || close_paren < open_paren)
  /* He удалось найти скобки, завершаем работу. */
  return NULL;
 /* Выделение памяти для результирующей строки */
 result = (char*)xmalloc(close_paren — open_paren);
 /* Копирование имени программы в буфер. */
 strncpy(result, open_paren + 1, close_paren - open_paren — 1);
 /* Функция strncpy() не завершает строку нулевым символом,
    приходится это делать самостоятельно. */
 result[close_paren - open_paren - 1] = '';
 /* Конец работы. */
 return result;
}
/* Эта функция определяет размер (в килобайтах) резидентной
   части процесса с заданным идентификатором.
   В случае ошибки возвращается -1. */
static int get_rss(pid_t pid) {
 char file_name[64];
 int fd;
 char mem_info[128];
 int rval;
 int rss;
 /* Генерируем имя файла "statm", находящегося в каталоге
    данного процесса в файловой системе proc. */
 snprintf(file_name, sizeof(file_name), "/proc/%d/statm",
  (int)pid);
 /* Открытие файла. */
 fd = open(file_name, O_RDONLY);
 if (fd == -1)
  /* Файл не удалось открыть. Возможно, процесс больше не
     существует. */
  return -1;
 /* Чтение содержимого файла. */
 rval = read(fd, mem_info, sizeof(mem_info) — 1);
 close(fd);
 if (rval <= 0)
  /* Файл не удалось прочитать, завершаем работу. */
  return -1;
 /* Завершаем прочитанный текст нулевым символом. */
 mem_infо[rval] = '';
 /* Определяем размер резидентной части процесса. Это второй
    элемент файла. */
 rval = sscanf(mem_info, "%*d %d", &rss);
 if (rval != 1)
  /* Содержимое файла statm отформатировано непонятным
     образом. */
  return -1;
 /* Значения в файле statm приведены в единицах, кратных размеру
    системной страницы. Преобразуем в килобайты. */
 return rss * getpagesize() / 1024;
}
/* Эта функция генерирует строку таблицы для процесса
   с заданным идентификатором. Возвращаемый буфер должен
   удаляться в вызывающей функции, в случае ошибки
   возвращается NULL. */
static char* format_process_info(pid_t pid) {
 int rval;
 uid_t uid;
 gid_t gid;
 char* user_name;
 char* group_name;
 int rss;
 char* program_name;
 size_t result_length;
 char* result;
 /* Определяем идентификаторы пользователя и группы, которым
    принадлежит процесс. */
 rval = get_uid_gid(pid, &uid, &gid);
 if (rval != 0)
  return NULL;
 /* Определяем размер резидентной части процесса. */
 rss = get_rss(pid);
 if (rss == -1)
  return NULL;
 /* Определяем имя исполняемого файла процесса. */
 program_name = get_program_name(pid);
 if (program_name == NULL)
  return NULL;
 /* Преобразуем идентификаторы пользователя и группы в имена. */
 user_name = get_user_name(uid);
 group_name = get_group_name(gid);
 /* Вычисляем длину строки, в которую будет помещен результат,
    и выделяем для нее буфер. */
 result_length =
  strlen(program_name) + strlen(user_name) +
  strlen(group_name) + 128;
 result = (char*)xmalloc(result_length);
 /* Форматирование результата. */
 snprintf(result, result_length,
  "<tr><td align=" right">%d</td><td><tt>%s</tt></td><td>%s</td>"

  "<td>%s</td><td align= "right">%d</td></tr>n",
  (int)pid, program_name, user_name, group_name, rss);
 /* Очистка памяти. */
 free(program_name);
 free(user_name);
 free(group_name);
 /* Конец работы. */
 return result;
}
/* HTML-код начала страницы, содержащей таблицу процессов. */
static char* page_start =
 "<html>n"
 " <body>n"
 "  <table cellpadding="4" cellspacing="0" border="1">n"
 "   <thead>n"
 "    <tr>n"
 "     <th>PID</th>n"
 "     <th>Program</th>n"
 "     <th>User</th>n"
 "     <th>Group</th>n"
 "     <th>RSS&nbsp;(KB)</th>n"
 "    </tr>n"
 "   </thead>n"
 "   <tbody>n";
/* HTML-код конца страницы, содержащей таблицу процессов. */
static char* page_end =
 "   </tbody>n"
 "  </table>n"
 " </body>n"
 "</html>n";
void module_generate(int fd) {
 size_t i;
 DIR* proc_listing;
 /* Создание массива iovec. В этот массив помещается выходная
    информации, причем массив может увеличиваться динамически. */
 /* Число используемых элементов массива */
 size_t vec_length = 0;
 /* выделенный размер массива */
 size_t vec_size = 16;
 /* Массив элементов iovec. */
 struct iovec* vec =
  (struct iovec*)xmalloc(vec_size *
  sizeof(struct iovec));
 /* Сначала в массив записывается HTML-код начала страницы. */
 vec[vec_length].iov_base = page_start;
 vec[vec_length].iov_len = strlen(page_start);
 ++vec_length;
 /* Получаем список каталогов в файловой системе /proc. */
 proc_listing = opendir("/proc");
 if (proc_listing == NULL)
  system_error("opendir");
 /* Просматриваем список каталогов. */
 while (1) {
  struct dirent* proc_entry;
  const char* name;
  pid_t pid;
  char* process_info;
  /* Переходим к очередному элементу списка. */
  proc_entry = readdir(proc_listing);
  if (proc_entry == NULL)
   /* Достигнут конец списка. */
   break;
  /* Если имя каталога не состоит из одних цифр, то это не
     каталог процесса; пропускаем его. */
  name = proc_entry->d_name;
  if (strspn(name, "0123456789") != strlen(name))
   continue;
  /* Именем каталога является идентификатор процесса. */
  pid = (pid_t)atoi(name);
  /* генерируем HTML-код для строки таблицы, содержащей
     описание данного процесса. */
  process_info = format_process_info(pid);
  if (process_info == NULL)
   /* Произошла какая-то ошибка. Возможно, процесс уже
      завершился. Создаем строку-заглушку. */
   process_info =
    "<tr><td colspan="5">ERROR</td></tr>";
  /* Убеждаемся в том, что в массиве iovec достаточно места
     для записи буфера (один элемент будет добавлен в массив
     по окончании обработки списка процессов). Если места
     не хватает, удваиваем размер массива. */
  if (vec_length == vec_size - 1) {
   vec_size *= 2;
   vec = xrealloc(vec, vec_size - sizeof(struct iovec));
  }
  /* Сохраняем в массиве информацию о процессе. */
  vec[vec_length].iov_base = process_info;
  vec[vec_length].iov_len = strlen(process_info);
  ++vec_length;
 }
 /* Конец обработки списка каталогов */
 closedir(proc_listing);
 /* Добавляем HTML-код конца страницы. */
 vec[vec_length].iov_base = page_end;
 vec[vec_length].iov_len = strlen(page_end);
 ++vec_length;
 /* Передаем всю страницу клиенту. */
 writev(fd, vec, vec_length);
 /* Удаляем выделенные буферы. Первый и последний буферы
    являются статическими, поэтому не должны удаляться. */
 for (i = 1; i < vec_length - 1; ++i)
  free(vec[i].iov_base);
 /* Удаляем массив iovec. */
 free(vec);
}

Задача сбора информации о процессах и представления ее в виде HTML-таблицы разбивается на ряд более простых операций.

? Функция get_uid_gid() возвращает идентификатор пользователя и группы, которым принадлежит процесс. Для этого вызывается функция stat() (описана в приложении Б, "Низкоуровневый ввод-вывод"), берущая информацию из каталога процесса в файловой системе /proc.

? Функция get_user_name() возвращает имя пользователя, соответствующее заданному идентификатору. Она просто вызывает библиотечную функцию getpwuid(), которая обращается к файлу /etc/passwd и возвращает копию строки из него. Функция get_group_name() находит имя группы по заданному идентификатору. Она вызывает функцию getgrgid().

? Функция gеt_program_name() возвращает имя программы, соответствующей заданному процессу. Эта информация извлекается из файла stat, находящегося в каталоге процесса в файловой системе /proc (см. раздел 7.2, "Каталоги процессов"). Мы поступаем так, а не проверяем символические ссылки exe или cmdline, поскольку последние недоступны, если серверный процесс не принадлежит тому же пользователю, что и проверяемый процесс.

? Функция get_rss() определяет объем резидентной части процесса. Эта информация содержится во втором элементе файла statm (см. раздел 7.2.6, "Статистика использования процессом памяти"), находящегося в каталоге процесса в файловой системе /proc.

? Функция format_process_info() генерирует набор HTML-тэгов для строки таблицы, представляющей заданный процесс. Здесь вызываются все вышеперечисленные функции.

? Функция module_generate() генерирует HTML-страницу с таблицей процессов. Выводная информация включает начальный HTML-блок (переменная page_start), строки с информацией о процессах (создаются функцией format_process_info()) и конечный HTML-блок (переменная page_end).

Функция module_generate() определяет идентификаторы процессов, проверяя содержимое файловой системы /proc. Для получения и анализа списка каталогов вызываются функции opendir() и readdir() (описаны в приложении Б, "Низкоуровневый ввод-вывод''). Из данного списка отбираются элементы, имена которых состоят из одних цифр: это каталоги процессов.

Поскольку в таблице может содержаться достаточно большое число строк, последовательная запись их в сокет с помощью функции write() приведет к ненужному повышению трафика. Для оптимизации числа передаваемых пакетов используется функция writev() (описана в приложении Б, "Низкоуровневый ввод-вывод"). Для нее создается массив vec, состоящий из элементов типа iovec. Так как число процессов не известно заранее приходится начинать с маленького массива и увеличивать его по мере необходимости. В переменной vec_length содержится число используемых элементов массива vec, а в переменной vec_size — число выделенных элементов. Когда эти переменные становятся почти равными друг другу, размер массива удваивается с помощью функции xrealloc(). По окончании работы с массивом удаляются все адресуемые в нем строки, а также сам массив.

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


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