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

Страницы работы

Содержание работы

Задание 1: Синтаксический анализ файлов

Вариант А: Задан файл (условно, база данных пользовательских учетных записей UNIX) следующего формата:

# comment8

8

username:password:UID:GID:usercomment:home:shell8

username:password:UID:GID:comment:home:shell8

. . .

username:password:UID:GID:comment:home:shell8

#        — символ начала комментария,

8        — символ разделителя записей,

:        — символ разделителя полей записи,

comment  — незначащий комментарий,

username — имя пользователя (строка),

password — пароль пользователя (строка),

UID      — идентификатор пользователя (короткое целое),

GID      — идентификатор первичной группы пользователя (короткое целое),

comm     — комментарий к учетной записи пользователя (строка),

home     — путь к домашнему каталогу пользователя (строка),

shell    — путь к начальному интерпретатору пользователя (строка).

Разработать определения типов uid_t, gid_t и следующие функции доступа к записям базы данных:

passwd* getpwnam(char *name);— поиск записи по имени пользователя name;

passwd* getpwuid (uid_t uid);— поиск записи по идентификатору пользователя uid;

passwd* getpwent(); — чтение очередной записи из базы данных;

Функции должны возвращать указатель на структуру следующего вида:

struct passwd

{

char *pw_name;

char *pw_passwd;

uid_t  pw_uid;

gid_t  pw_gid;

char *pw_gecos;

char *pw_dir;

char *pw_shell;

};

Пример разработки сверху вниз

При создании кода надо руководствоваться логикой общения с пользователем. Мысленно проигрывайте желаемый сценарий работы с ним и постепенно добивайтесь его воплощения. Начните разработку с создания удобных структур данных. На этой стадии надо учитывать платформу, предпочтения, оказываемые некоторым библиотечным функциям, желаемый стиль и т.д. Предположим, что нам надо разработать приложение в стиле 70-х годов прошлого века на основе функций библиотеки CRT, прототипы которых описаны в файле заголовков stdio.h. Минимальный набор runtime-библиотек задаем в файле stdafx.h:

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

#include <string.h>

В файле Users.cpp:

¨  Объявляем новые типы данных. В нашем случае они определены и заданы заказчиком,

¨  Объявляем глобальные структуры данных — они вычисляются на основе опыта разработки с учетом частоты использования всеми функциями приложения.

#include "stdafx.h"

typedef unsigned short uid_t; // Глобальные типы данных

typedef unsigned short gid_t;

struct passwd                  // Тип структур для описания пользователя

{

char *pw_name;

char *pw_passwd;

uid_t  pw_uid;

gid_t  pw_gid;

char *pw_gecos; // Comment

char *pw_dir;   // Home

char *pw_shell;

};

//===== Глобальные переменные

FILE *fp;                   // Файловый указатель

const int maxLen = 128;      // Ограничитель длины строки при вводе данных

char buf[maxLen+maxLen];     // Буфер должен вместить данные и сообщение

long posUsers;               // Позиция начала данных в файле (без учета заголовка)

char extension[] = ".users"; // Предполагаемое расширение файла с данными

char defaultFile[] = "Test.users";  // Тестовый файл с данными

const char delim = ':';     // Символ разделителя полей

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

void main()

{

puts ("Users Service. Getting info from users files\n");

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

{

  char *fileName = 0;

  try

  {

    fileName = GetFileName();  // Выясняем имя файла с учетными записями

    OpenUserFile (fileName);   // Попытка открыть файл

    QueryUser();               // Диалог, запрашивающий и выполняющий операцию с данными

  }

  catch (char* s)

  {

    if (strcmp(s, "Stop") == 0) // Нормальное завершение приложения

    {

      printf ("Users service: Bye\n");

      if (fp)

        fclose(fp);

      break;

    }

    printf ("\nError: %s", s);    // Ненормальное завершение приложения

    if (No ("\nTry again? (Y/N)"))

      break;

    delete [] fileName;

    fileName = 0;

  }

}

puts ("\n\n");

}

Здесь определены правила игры с функциями: GetFileName, OpenUserFile и QueryUser, No. Их предстоит разработать. Вставив вызовы несуществующих функций, мы задали желаемые сигнатуры новых строительных кирпичей программы. В процессе создания кода придется реализовать еще какие-то другие операции с данными. При этом используется тот же самый прием: добавить вызовы несуществующих функций, затем приступить к их разработке. Теперь попытаемся разработать новые функции. Их надо либо целиком вставлять перед функцией main, либо вставить их прототипы, а затем давать тела (уже в любом порядке).

Я предпочитаю первый стиль — работать без прототипов. При организации диалога с пользователем распространен такой прием — использовать для временного хранения данных общий буфер (глобальный массив buf). Удобство в том, что его не надо чистить. Некоторые данные, введенные пользователем, удобно отложить в сторону, то есть скопировать их в динамически созданный массив (который надо чистить). В связи с этим целесообразно создать несколько служебных функций (своих маленьких утилит):

bool No (char* prompt) // Просим выбрать ответ: Yes or No

{

printf ("\n%s: ", prompt);

return toupper (getche()) != 'Y';

}

bool Yes (char* prompt)  // Просим выбрать ответ: Yes or No

{

printf ("\n%s: ", prompt);

return toupper (getche()) != 'N';

}

char* CopyString()   // Часто используемая функция. Она копирует буфер в новую память

{

return strcpy (new char[strlen(buf)+1], buf);

}

char* GetLine (char *prompt) // Просим ввести в буфер buf строку (не более maxLen символов)

{

printf ("%s: ",prompt);   // Подсказка пользователю (Что мы у него запрашиваем)

fflush (stdin);           // Чистим буфер клавиатуры

char c;  int i;

for (i = 0;  i<maxLen && (c = getchar()) != EOF && c != '\n';  i++) // Посимвольный ввод

  buf[i] = c;    // Помещаем символ в буфер buf

buf[i] = 0;     // Завершаем строку нулем

return buf;

}

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

char* GetString (char* prompt)  // Просим ввести строку символов и возвращаем ее копию

{

GetLine (prompt);

return CopyString();

}

short SetShort (char *s) // Преобразуем строку символов (из буфера) в короткое целое

{

char *stop;

long id = strtol (s, &stop, 0);

if (*stop != 0 || id > 0xffff)

  throw "SetShort: Wrong ID field";

if (s != buf)

  delete [] s;

return (short)id;

}

Следующая функция понадобится при реализации диалога с пользователем.

char Menu()

{

puts (

  "\n\n\tFind a user:\n"

  "\n\tn - By name"

  "\n\ti - By ID"

  "\n\tq - Quit\n");

return getch();

}

Приступим к разработке функций, запланированных при создании главной функции main.

char* GetFileName()

{

//===== Просим ввести имя файла или завершить диалог (вводом символа q)

GetLine ("\n\n\tEnter a users file name (q - quit)");

int len = int(strlen(buf));

if (len == 1 && toupper(buf[0]) == 'Q')    // Нормальное завершение приложения

  throw "Stop";

char* fileName = 0;

if (len == 0)     // Если пользователь не задал имя файла, откроем defaultFile

  fileName = strcpy (new char[strlen(defaultFile) + 1], defaultFile);

else

{

  int n = int(strlen (extension));

  // Если пользователь не задал расширения, дополним имя файла расширением extension

Похожие материалы

Информация о работе