Синтаксический анализ файлов базы данных пользовательских учетных записей UNIX, страница 2

  if (len > n && strcmp (buf + len - n, extension) == 0)

    fileName = CopyString();   // Создаем новую строку на основе данных буфера

  else

    fileName = strcat (strcpy (new char[len + n + 1], buf), extension);

}

return fileName;

}

Ранее мы рассматривали, как причесать неопрятную строку символов, то есть привести ее к каноническому виду, в котором отсутствуют начальные и конечные пробелы, а слова разделяются лишь одним пробелом. Создайте код функции Trim, которая осуществляет этот план.

void Trim (char* text) // Приводит строку символов к нормальному виду

{

bool was = true; // Флаг, указывающий, что предыдущий символ был пустой (пробел)

//====== Ваш код. Не забудьте завершить (нулем) новую каноническую строку

}

В бесконечном диалоге с пользователем важно освобождать память, занимаемую текущей структурой типа passwd. Разработайте код функции DeleteUser, которая освобождает поля структуры, а затем и саму структуру.

void DeleteUser (passwd *user)

{

//====== Ваш код

}

Функцию вывода данных о пользователе приведем полностью, так как она тривиальна.

void ShowUser (passwd* user)

{

if (user)

{

  char line[] = "\n----------------------------------------------";

  printf ("%s\n"

    "User:\t %s\n"

    "Pass:\t %s\n"

    "UID:\t %d\n"

    "GID:\t %d\n"

    "Home:\t %s\n"

    "Shell:\t %s\n"

    "Comment: %s%s",

    line, user->pw_name, user->pw_passwd, user->pw_uid,

    user->pw_gid, user->pw_dir, user->pw_shell, user->pw_gecos, line);

}

}

Вернемся к одному из кирпичиков, который был использован в главной программе.

void OpenUserFile (char *fileName) // Пытаемся открыть файл и прочесть его заголовок

{

// Откройте файл c именем fileName и присвойте файловый указатель глобальной переменной fp

// Если не удалось, то выбросьте исключение типа "Couldn't open " + fileName

// Операцию "плюс" реализуйте с помощью функции strcat

ReadToken ("while reading file comment"); // Пытаемся прочесть заголовок файла (File Comment)

// Строка на входе: "while reading file

 comment" будет выброшена в случае неудачи (конца файла)

printf ("\nOpenung file:\t%s\nFile comment:\t%s", fileName, buf);

ReadToken ("while reading file header");   // Пытаемся прочесть разделитель заголовка и списка

// Запомните файловую позицию начала списка в переменной posUsers

}

Функция ReadToken — одна из главных в функционировании приложения. Она читает из файла одну лексему. Важно помнить, что лексема ограничена либо разделителем (delim = ':'), либо символом конца строки ('\n').

void ReadToken (char* when) // Чтение очередной лексемы

{

int n = 0;  // Счетчик символов

// Создайте цикл с логикой: Читаем, пока позволяет файл и буфер

{          

  char c = // Считываем один символ

  // Если он не равен delim и не равен символу конца строки, то заносим его в буфер buf

  // Иначе завершаем строку в буфере (нулем), вызываем Trim и уходим

}// Конец цикла

if (n == maxLen) // Если цикл завершился аварийно, то выбрасываем причину (строка when)

  throw strcat (strcpy (buf, "ReadToken: Token is too long"), when);

else

  throw strcat (strcpy (buf, "ReadToken: EOF "), when);

}

Удобно иметь еще одну функцию чтения лексемы из файла в буфер. Она создает и возвращает копию лексемы.

char* GetToken (char* when) // Читаем лексему и возвращаем ее копию

{

ReadToken (when);

return CopyString();

}

Следующий строительный кирпич нужен для работы с индексами (pw_uid и pw_gid).

short GetShort(char* when)  // Читаем лексему, преобразуем ее в short и возвращаем

{

ReadToken (when);

return SetShort (buf);

}

Одна из самых сложных функций — это чтение файловых адресов: pw_dir и pw_shell. Сложность состоит в том, что в этой лексеме может присутствовать символ ':' (например, D:\Users\). Вспомните, что этот же символ является разделителем полей в нашей "базе данных". Надо как-то выявить файловый разделитель и не путать его с  разделителем полей. Для этого пришлось разработать отдельную функцию GetPath.

char* GetPath()  // Читает лексему, в которой может быть разделитель :

{

ReadToken ("while reading path drive"); // Читаем обычную лексему

if (strlen(buf) == 1)

{

  char *disk = 0, *path = 0;

  try

  {

    disk = strdup (buf);   // Копируем лексему (имя диска)

    ReadToken ("while reading path");

    path = strdup (buf);   // Копируем лексему (остальная часть файлового пути)

    strcat (strcat (strcpy (buf, disk), ":"), path);

    free (disk); free (path);

  }

  catch (char* s)

  {

    free (disk); free (path); // Особенности работы с функцией strdup (см. MSDN)

    throw s;

  }

}

return CopyString();

}

Разработаем еще один кирпич, запланированный в главной программе.

void QueryUser()   // Диалог с пользователем

{

passwd* user = 0;

// Создайте бесконечный цикл (диалог с пользователем)

{

  char *name = 0;

  switch (Menu())    // Предложение альтернатив (искать запись по имени или по индексу)

  {

  case 'n': user = GetUserByName (GetString ("Enter a user name"));

    break;

  case 'i': user = GetUserById (SetShort(GetLine ("Enter a user id")));

    break;

case 'q': throw "Stop";  // Нормальное завершение приложения

default: if (No ("Want to quit ? (Y/N)"))

             continue;

  }

  // Выведите информацию о пользователе c адресом user

  // Верните файловый указатель в начальную позицию

  getch();

}

}

Следующая функция запланирована заказчиком. Мы изменили имя с учетом принятых норм (design guidelines).

passwd* GetUser()    // Чтение информации о текущем пользователе

{

// Объявите и создайте новую структуру (user), описывающую пользователя

// Обнулите все ее поля

try

{

  user->pw_name    = GetToken ("name");

  user->pw_passwd    = GetToken ("while reading user pass");

  user->pw_uid     = GetShort ("while reading user ID");

  user->pw_gid     = GetShort ("while reading group ID");

  user->pw_gecos   = GetToken ("while reading user comment");

  user->pw_dir     = GetPath();

  user->pw_shell   = GetPath();

}

catch (char* s)

{

  // Освободите память, занятую структурой, описывающей пользователя

  if (strcmp (s, "ReadToken: EOF name") == 0) // Ожидаемое завершение приложения

    return 0;

  throw s;     // Неожиданный конец файла

}

return user;

}

Эта функция также задана заказчиком.

passwd* GetUserByName (char *name)    // Поиск строки, содержащей искомое имя name

{

printf ("\nLooking for:\t%s\n", name);

passwd *user;

// Создайте цикл с логикой: Читаем, пока есть пользователи

for (int i=1; (пока получаем пользователей) != 0;  i++) // Пока не кончился файл (ожидаемо)

{

  // Если имя текущего пользователя совпадает с искомым name

  {

    printf ("\n#%d. Found a user with the name = %s", i, name);

    // Освободите память, занятую name

    // Верните user

  }

  // Освободите память, занятую user

}

printf ("User: %s - not found\n", name);

return 0;

}

Логика поиска по индексу аналогична логике поиска по имени, поэтому создать код GetUserById несложно.

passwd* GetUserById (uid_t uid) // Поиск строки, содержащей искомый индекс uid

{

printf ("\nLooking for:\t%d\n", uid);

passwd *user;

// Создайте цикл с логикой: Читаем, пока есть пользователи

// и т.д.

printf ("User ID: %d - not found\n", uid);

return 0;

}

Соберите вместе все части приложения и отладьте его. Файл с данными вы можете взять в папке курса.