Сервер Novell NetWare
В этой главе мы кратко рассмотрим общие принципы взаимодействия файл-сервера и рабочих станций.1.2. Рабочая станция Novell NetWare
Сервер Novell NetWareРабочая станция Novell NetWare
Состав сетевых функций
Создание и удаление каналов
Работа с томами и каталогами файл-сервера
Работа с файлами
Передача данных между рабочими станциями
Синхронизация программ
Работа с базой данных объектов
Управление файл-сервером
Прочие функции
Сервер Novell NetWare
В томе "Библиотеки системного программиста", посвященном установке аппаратного и программного сетевого обеспечения, мы рассказывали вам о том, что локальная сеть объединяет между собой несколько компьютеров и что часть этих компьютеров в сети Novell NetWare обязательно выделяется для работы в качестве файл-серверов. На остальных компьютерах работают пользователи.
Программное обеспечение файл-сервера сети Novell NetWare состоит из сетевой операционной системы NetWare. Эта операционная система работает в мультизадачном режиме реального времени, обеспечивая совместное использование ресурсов файл-сервера рабочими станциями.
В рамках NetWare одновременно работают несколько процессов. В версии 2.2 используются VAP-процессы, в версии 3.11 - динамически подгружаемые NLM-процессы. Эти процессы (или программы) через драйвер сетевого адаптера и с использованием протокола передачи данных IPX взаимодействуют с программным обеспечением рабочих станций, предоставляя им файловый сервис, а также другие виды сервиса.
Например, в качестве отдельного процесса на файл-сервере может работать сервер печати, обеспечивающий коллективный доступ к принтеру, или сервер СУБД, получающий запросы от рабочих станций и выполняющий поиск в базе данных, расположенной на дисках файл-сервера.
Таким образом, рабочие станции при помощи протокола IPX посылают в файл-сервер запросы. Соответствующий процесс, запущенный на файл-сервере, их обрабатывает и в случае необходимости посылает результат рабочей станции, пользуясь все тем же протоколом IPX.
Теоретически ваша программа, запущенная на рабочей станции, может сама посылать все необходимые запросы файл-серверу через протокол IPX и обрабатывать полученные в ответ пакеты данных. Однако на практике так никогда не делают. Для работы с файл-сервером используют специальную программу, называемую сетевой оболочкой рабочей станции. Эта оболочка предоставляет программам интерфейс достаточно высокого уровня, который в основном и является предметом изучения в данной книге.
Рабочая станция Novell NetWare
Сетевое программное обеспечение рабочей станции Novell NetWare в среде MS-DOS состоит из нескольких резидентных программ: программы поддержки протоколов передачи данных lsl.com, драйвера сетевого адаптера (например, ne2000.com), драйвера протоколов IPX/SPX ipxodi.exe и, наконец, сетевой оболочки netx.exe.
Резидентная программа netx.exe обеспечивает интерфейс с файл-серверами, перехватывая прерывания INT21h, INT 24h и INT 17h.
Прерывание INT 21h в отсутствие сетевой оболочки реализует стандартный набор функций MS-DOS, который мы подробно рассматривали в первом томе "Библиотеки системного программиста". Программа netx.exe перехватывает некоторые функции MS-DOS, например функции, предназначенные для работы с дисками и файлами. Если программа работает с локальным диском, сетевая оболочка передает управление соответствующим функциям MS-DOS. Если же программа пытается обратиться к дискам сервера, все запросы через протокол IPX адресуются соответствующему серверу. Так как, обращаясь к дискам, программа вызывает обычные функции MS-DOS, она может и не знать, что работает не с локальным диском, а с диском сервера.
Кроме перехвата имеющихся функций MS-DOS сетевая оболочка добавляет новые функции, предназначенные в основном для работы с файл-сервером. Практически весь описанный в этой книге интерфейс с файл-сервером Novell NetWare реализован как расширение стандартного набора функций MS-DOS в рамках прерывания INT 21h.
Прерывание INT 24h используется операционной системой MS-DOS для обработки критических ошибок. Сетевая оболочка расширяет возможности обработчика критических ошибок MS-DOS.
Прерывание INT 17h - это прерывание BIOS, предназначенное для печати на принтере. Перехватывая это прерывание, сетевая оболочка может переназначать печать с локального принтера на удаленный сетевой принтер.
Таким образом, прикладная программа, запущенная на рабочей станции, может посылать запросы серверу через дополнительные (сетевые) функции прерывания INT 21h, встраиваемые сетевой оболочкой. Сетевые функции при необходимости сами формируют IPX-пакеты и передают их нужному файл-серверу, а также принимают ответные пакеты. Сама же прикладная программа редко работает с файл-сервером на уровне IPX-пакетов, так как интерфейс сетевой оболочки значительно удобнее.
По назначению сетевые функции можно разбить на следующие группы:
функции для создания и удаления каналов между рабочими станциями и файл-серверами; функции для работы с томами и каталогами файл-сервера; функции для работы с файлами; функции для передачи данных между рабочими станциями; функции для синхронизации программ, работающих одновременно на разных рабочих станциях; функции для работы с базой данных объектов; функции для управления файл-сервером; прочие функции.
В нашей книге мы рассмотрим использование основных функций, входящих в перечисленные выше группы, и приведем исходные тексты программ, демонстрирующих работу с ними.
Создание и удаление каналов
1.3.1. Создание и удаление каналов
Прежде чем сетевая оболочка сможет получить сервис от файл-сервера, она должна установить с файл-сервером канал связи (connection). Оболочка рабочей станции может создать до восьми каналов с восемью различными файл-серверами. Количество каналов, которые может создать файл-сервер с рабочими станциями, зависит от версии и конкретной конфигурации сетевой операционной системы Novell NetWare. Например, если вы приобрели версию NetWare на 20 пользователей, ваш файл-сервер может создать не более 20 каналов с рабочими станциями.
Как файл-сервер, так и оболочка рабочей станции имеют собственные таблицы каналов, в которых записана необходимая информация о каждом канале (например, сетевые адреса файл-серверов).
В данной группе есть функции для создания каналов с файл-серверами, для удаления этих каналов, а также для получения информации из таблицы каналов.
При создании канала с файл-сервером сетевая оболочка не сообщает серверу имя и пароль пользователя. Это связано с тем, что создание канала еще не означает получения полного доступа к серверу. Программа, создавшая канал с файл-сервером, еще не имеет доступа к его дискам. Для получения доступа к ресурсам файл-сервера необходимо выполнить процедуру подключения пользователя к файл-серверу с помощью специально предназначенной для этого функции. Функции подключения требуется указать имя пользователя и пароль, назначенные супервизором сети или руководителем группы.
Разумеется, имеются функции для отключения пользователя от одного файл-сервера или от всех файл-серверов сразу.
Работа с томами и каталогами файл-сервера
1.3.2. Работа с томами и каталогами файл-сервера
В составе сетевой оболочки имеются функции, предназначенные для отображения локальных дисков рабочей станции на тома и каталоги файл-сервера. Сетевая оболочка использует несколько таблиц для обеспечения такого отображения.
Когда программа, запущенная на рабочей станции, обращается к диску, сетевая оболочка просматривает таблицы отображения и определяет, к какому диску - локальному или сетевому - выполняется обращение. Если программа обращается к локальному диску, сетевая оболочка вызывает соответствующую функцию MS-DOS. Если же программа обращается к диску, отображенному на том или каталог файл-сервера, сетевая оболочка при помощи протокола IPX отправляет запрос соответствующему файл-серверу.
В состав данной группы входят многочисленные функции для отображения дисков, для создания, переименования и удаления каталогов, для изменения прав доступа пользователей к каталогам, для получения различной справочной информации о правах доступа, томах, каталогах и о содержимом каталогов.
Работа с файлами
1.3.3. Работа с файлами
Работа с файлами, расположенными на дисках файл-сервера, имеет ряд особенностей. Вам необходимо учитывать возможные ограничения прав доступа, возможность одновременного доступа к файлу со стороны нескольких рабочих станций.
Поэтому дополнительно к обычным функциям MS-DOS, предназначенным для работы с файлами, сетевая оболочка добавляет новые, учитывающие специфику работы с файл-сервером.
Передача данных между рабочими станциями
1.3.4. Передача данных между рабочими станциями
Сетевая оболочка содержит функции для передачи данных между рабочими станциями через память файл-сервера. Пользоваться этими функциями значительно проще, чем функциями драйвера протоколов IPX/SPX, но и скорость передачи данных средствами сетевой оболочки ниже.
Вы можете организовать передачу сообщений или создать канал (pipe) между программами, запущенными на различных рабочих станциях.
Синхронизация программ
1.3.5. Синхронизация программ
Для синхронизации программ, запущенных на различных рабочих станциях, сетевая оболочка использует механизм семафоров. Семафоры физически расположены на файл-сервере. Сетевая оболочка предоставляет программам функции для изменения или проверки состояния семафоров.
С каждым семафором логически можно связать какой-либо критический ресурс, совместно используемый различными рабочими станциями, например модем или принтер. Захватив управление ресурсом, одна из рабочих станций устанавливает соответствующий семафор. Когда другой станции потребуется доступ к этому ресурсу, она должна опросить состояние семафора. Если семафор установлен, рабочая станция должна ждать освобождения ресурса.
Работа с базой данных объектов
1.3.6. Работа с базой данных объектов
Файл-сервер Novell NetWare хранит информацию о пользователях и ресурсах в специальной базе объектов, которая называется BINDERY. В этой базе хранятся имена пользователй, пароли, права доступа к объектам и другая информация. Сетевая оболочка рабочей станции предоставляет программам все необходимые функции для работы с базой данных объектов, выполняющие добавление, извлечение, изменение и поиск информации в базе данных. Успех выполнения практически всех функций зависит от прав доступа вызывающей программы. Например, программа, подключившаяся к серверу на правах супервизора, может извлекать и изменять практически любые данные в базе BINDERY, в то время как обычному пользователю в основном доступны только функции, получающие сведения о самом себе.
Управление файл-сервером
1.3.7. Управление файл-сервером
Функции этой группы могут пригодиться вам для создания утилит оператора консоли и супервизора сети, так как они позволяют управлять файл-сервером и получать информацию о его состоянии. Вы сможете разрешать или запрещать подключение пользователей к системе, передавать сообщения одновременно на все рабочие станции, устанавливать системные часы файл-сервера, отключать пользователей от файл-сервера, останавливать работу файл-сервера и т. д. Практически все функции управления для своего использования требуют прав доступа оператора консоли или супервизора.
Прочие функции
1.3.8. Прочие функции
К этой группе мы отнесем все остальные функции, такие, как функции управления системой отката транзакций (TTS), функции для обслуживания рабочих станций Apple и некоторые другие. Так как объем книги ограничен, мы не сможем описать все функции сетевой оболочки.
Состав сетевых функций
Прежде чем мы перейдем к детальному изучению возможностей сетевой оболочки, сделаем краткий обзор основных групп сетевых функций.
Проверка присутствия сетевой оболочки
2.1. Проверка присутствия сетевой оболочки
2.3. Создание канала с файл-сервером
2.4. Подключение к файл-серверу
В этой главе мы рассмотрим процедуры получения списка активных серверов в сети и подключения пользователей к серверам. Вы научитесь составлять программы, выполняющие действия, аналогичные сетевым утилитам slist.exe, login.exe и attach.exe. Эти утилиты были подробно описаны нами в томе "Библиотеки системного программиста", посвященном установке аппаратного и программного обеспечения локальных сетей компьютеров.
Пользователь подключается к файл-серверу обычно при помощи утилит login.exe и attach.exe, которые поставляются в комплекте с сетевой операционной системой Novell NetWare. Программа login.exe подключает пользователя только к одному серверу. Если необходимо подключиться сразу к нескольким серверам, это можно сделать при помощи программы attach.exe.
Программа login.exe прежде всего создает канал с сервером, записывая данные о сервере в две внутренние таблицы сетевой оболочки - в таблицу номеров каналов (Connection ID Table) и в таблицу имен серверов (Server Name Table). Для этой процедуры не нужны имя пользователя и пароль, запрашиваемые утилитой login.exe. Заметим, что сама сетевая оболочка netx.exe при запуске создает канал с ближайшим сервером. Этот сервер с точки зрения сетевой оболочки становится первичным (Primary) сервером. После создания канала на рабочей станции появляется новый диск, отображаемый на сетевой каталог SYS:LOGIN. В этом каталоге есть две программы - slist.exe и login.exe, предназначенные соответственно для получения списка серверов и для подключения пользователя к серверу.
Затем утилита login.exe пытается подключить пользователя к серверу, проверяя имя пользователя и пароль. Если база данных объектов сервера содержит пользователя с введенным именем и паролем, пользователь получает доступ к файл-серверу.
Файл-сервер имеет таблицу каналов (File Server Connection Table) и таблицу паролей (Password Table). После того как сетевая оболочка создает канал с сервером, в таблицу каналов файл-сервера записывается номер канала, используемого сервером для связи с рабочей станцией. Разумеется, номер канала на рабочей станции не равен номеру канала на сервере. Рабочая станция может создать до 8 каналов с серверами, а сервер - до 250 (в зависимости от версии сетевой операционной системы).
Если подключение пользователя к файл-серверу завершилось успешно, Netware заносит идентификатор пользователя в таблицу паролей.
Программа attach.exe может создать новый канал с другим сервером, отличным от первичного, записав его номер в таблицу номеров каналов, а также подключить пользователя к еще одному серверу.
Подключив пользователя к файл-серверу, программа login.exe переходит в каталог SYS:PUBLIC и считывает системный файл автоматической настройки System Login Script, который находится в файле с именем net$log.dat. Программа login.exe интерпретирует все команды, записанные в этом файле.
Далее программа login.exe извлекает из базы данных BINDERY идентификатор пользователя и ищет в каталоге SYS:MAIL каталог с именем, совпадающим с десятичным представлением идентификатора пользователя, - личный каталог пользователя. В личном каталоге пользователя находится личный файл автоматической настройки Login Script с именем login (имя не имеет расширения). Если этот файл есть, программа login.exe считывает и интерпретирует его.
Только что мы рассмотрели упрощенный алгоритм работы программы login.exe, подключающей пользователя к файл-серверу. Если вам потребуется создать собственную программу подключения пользователей, вы должны выполнить все или некоторые из описанных выше действий.
Проверка присутствия сетевой оболочкиЛистинг 1
Поиск серверов в сети
Создание канала с файл-сервером
Подключение к файл-серверу
Программа SLIST
Пограмма LOG
Программа SLIST
2.4.1. Программа SLIST
Мы подготовили для вас программу, которая, пользуясь протоколом SAP, определяет список активных серверов и запоминает имена серверов. Затем для всех активных серверов программа получает дополнительную информацию и выводит ее в стандартный поток вывода.
Программа создает объект класса SLIST. Конструктор этого объекта получает всю необходимую информацию, которая при помощи функции SLIST::PrintServersName(), определенной в классе SLIST, выводится в стандартный поток (листинг 3). // ================================================================
Проверка присутствия сетевой оболочки
Прежде чем обращаться к функциям сетевой оболочки рабочей станции, ваша программа должна убедиться, что эта оболочка загружена. Следует также узнать ее версию, так как состав функций сетевой оболочки может меняться от версии к версии.
Для проверки присутствия сетевой оболочки и определения ее версии проще всего воспользоваться функцией GetShellVersionInformation(), входящей в состав библиотеки NetWare C Interface. Приведем прототип указанной функции: int GetShellVersionInformation(BYTE *MajorVersion, BYTE *MinorVersion, BYTE *RevisionLevel);
Тип BYTE описан в include-файле prolog.h, входящем в состав NetWare C Interface: #define BYTE unsigned char
Если функция возвратила значение 0xFF, поля MajorVersion, MinorVersion, RevisionLevel будут содержать соответственно верхний (major) номер версии, нижний (minor) номер версии и номер изменения (revision). Если функция GetShellVersionInformation() вернула нулевое значение, версия сетевой оболочки слишком стара (номер версии меньше 2.1) и не поддерживает данную функцию.
Для того чтобы определить наличие сетевой оболочки, перед вызовом функции GetShellVersionInformation() запишите нулевое значение в переменную MajorVersion. Если после возврата из функции переменная MajorVersion не изменила своего значения, сетевая оболочка не загружена.
К сожалению, функция GetShellVersionInformation() не сохраняет содержимое регистра SI, поэтому у нас возникли проблемы с использованием этой функции в среде сетевой оболочки версии 3.26. Мы вышли из положения простым способом - сохранили содержимое этого регистра в стеке сами перед вызовом функции, а после возврата из функции восстановили его.
Приведем программу, определяющую версию сетевой оболочки (листинг 1): // ===================================================
Поиск серверов в сети
Если в сети имеется более одного сервера, то, прежде чем подключиться к файл-серверу, вам необходимо узнать его имя, заданное супервизором при запуске сервера. Для этого предназначена утилита slist.exe, которая находится в каталоге SYS:LOGIN и всегда доступна, если на рабочей станции загружена сетевая оболочка.
Однако при создании собственных утилит вам может потребоваться сделать меню активных серверов в сети, поэтому следующим этапом после определения версии сетевой оболочки будет определение списка активных файл-серверов.
Для поиска серверов вы можете воспользоваться диагностическим сервисом, описанным в предыдущем томе "Библиотеки системного программиста". Однако существует более удобный протокол, позволяющий средствами IPX найти все активные серверы и, что самое главное, определить их имена. Этот протокол называется протоколом объявления сервиса в сети (Service Advertising Protocol - SAP).
Использование протокола SAP основано на том факте, что все серверы в сети идентифицируют себя периодической посылкой пакета IPX специального типа - пакета объявления сервиса (Servise Advertising Packet). Кроме того, рабочие станции и серверы могут посылать пакеты запроса (Service Query) по адресу 0xFFFFFFFFFFFF, в ответ на который все серверы присылают запросившей станции пакеты объявления сервиса. Последнее обстоятельство роднит сервис SAP с диагностическим сервисом.
Для того чтобы найти все активные серверы в сети, ваша программа должна подготовить массив буферов и блоков ECB для приема IPX-пакетов объявления сервиса и послать по адресу 0xFFFFFFFFFFFF пакет запроса на сокет 0x452. Через некоторое время программа получит пакеты объявления сервиса. Просмотрев их, она сможет определить имена серверов, а также другую информацию об активных серверах.
Пакет запроса состоит из стандартного IPX-заголовка и блока данных, который может быть описан структурой следующего вида: struct QPacket { unsigned QueryType; unsigned ServerType; };
Поле QueryType задает тип запроса и может содержать одно из двух значений: 1 или 3. Значение 1 соответствует общим запросам и позволяет получить информацию о всех серверах во всех сетях. Значение 3 позволяет найти ближайший сервер нужного типа.
Тип сервера, который нужно найти, задается в поле ServerType. Для определения значения, соответствующего файл-серверу, можно воспользоваться списком типов объектов, хранящихся в базе данных объектов сервера:
Значение | Описание |
0 | Не классифицируемый (неизвестный) объект |
1 | Пользователь |
2 | Группа пользователей |
3 | Очередь печати |
4 | Файл-сервер |
5 | Сервер заданий |
6 | Шлюз |
7 | Сервер печати |
8 | Очередь для архивирования |
9 | Сервер для архивирования |
A | Очередь заданий |
B | Администратор |
24 | Сервер удаленного моста |
Номера типов объектов назначаются фирмой Novell; при необходимости вы можете создавать объекты своих типов, если получите в Novell номер специально для создаваемого вами объекта.
Для поиска всех файловых серверов вам надо указать в поле ServerType значение 4, а в поле QueryType - значение 1.
После посылки пакета запроса вы получите несколько пакетов объявления типа, состоящих из обычного IPX-заголовка и блока данных в следующем формате (описанном в файле sap.h, входящем в библиотеку NetWare C Interface): typedef struct SAPHeader { WORD checksum; WORD length; BYTE transportControl; BYTE packetType; IPXAddress destination; IPXAddress source; WORD SAPPacketType; WORD serverType; BYTE serverName[48]; IPXAddress serverAddress; WORD interveningNetworks; } SAPHeader;
Тип WORD описан в include-файле prolog.h, входящем в состав NetWare C Interface: #define WORD unsigned int
Поля checksum, length, transportControl, packetType, destination и source представляют собой заголовок IPX-пакета. Тип IPXAddress описывает сетевой адрес и также определен в файле sap.h: typedef struct IPXAddress { BYTE network[4]; BYTE node[6]; WORD socket; } IPXAddress;
Все эти поля мы подробно описали в предыдущем томе "Библиотеки системного программиста".
Поле SAPPacketType содержит значение 2, если пакет пришел в ответ на общий запрос, или 4 - для ближайших запросов.
Поле serverType содержит номер типа сервера, описываемого данным пакетом. Если мы запрашивали информацию о файл-серверах, в этом поле должно быть значение 4.
В поле serverName расположена текстовая строка имени сервера, закрытая двоичным нулем. Именно это поле нам и нужно для получения списка имен активных серверов в сети.
Поле serverAddress является структурой, в которой находится сетевой адрес сервера. Именно по этому адресу сервер принимает запросы от сетевых оболочек рабочих станций. Вам не следует использовать в своих программах сокет, номер которого возвращается в поле serverAddress.socket, если вы не знаете точно, что и как собираетесь с ним делать.
Поле interveningNetworks отражает количество мостов, которые прошел пакет на своем пути от сервера до рабочей станции. Если значение в этом поле равно 1, сервер и рабочая станция находятся в одном сегменте сети.
Таким образом, самое интересное для нас в пакете объявления типа - это поля типа сервера serverType и имени сервера serverName. Для подключения к сети нам потребуются только имена файловых серверов.
В разделе "Программа SLIST" мы приведем исходный текст программы, выводящей на экран список активных серверов и другую интересную информацию о серверах, например серийные номера операционных систем. Для того чтобы вы смогли в ней разобраться, в мы расскажем вам в следующем разделе о каналах, создаваемых между сетевыми оболочками рабочих станций и серверами.
Создание канала с файл-сервером
Каналы, создаваемые между сетевыми оболочками рабочих станций и файл-серверами, похожи на каналы, создаваемые протоколом SPX (или протоколом NETBIOS). Однако для повышения производительности эти каналы сделаны на базе протокола IPX, а не на базе протокола SPX, как это можно было бы предположить.
И сервер, и каждая рабочая станция имеют таблицы номеров каналов, в которых находятся различные характеристики партнеров, такие, как имена или сетевые адреса. Таблица каналов рабочей станции содержит 8 элементов, поэтому каждая рабочая станция может подключиться не более чем к 8 различным серверам. Размер таблицы каналов файл-сервера может меняться в зависимости от версии операционной системы Novell NetWare в пределах от 5 до 250. Этот размер определяет максимальное количество пользователей, которые могут подключиться к файл-серверу.
Подробно формат таблицы номеров каналов и других таблиц сетевой оболочки мы рассмотрим в разделе "Отображение дисков рабочей станции" этой главы. Сейчас для нас важно, что при создании канала с файл-сервером в таблице номеров каналов появляется новая запись. При уничтожении канала соответствующая запись также удаляется.
Для создания канала с файл-сервером следует использовать функцию AttachToFileServer(), определенную в библиотеке Novell NetWare C Interface следующим образом: int AttachToFileServer(char *ServerName, WORD *ConnectionID);
Функции надо передать указатель на текстовую строку с именем файл-сервера и адрес переменной типа WORD, в которую будет записан номер созданного канала. При успешном создании канала функция возвращает нулевое значение, в противном случае - код ошибки:
Код ошибки | Значение |
0xF8 | Рабочая станция уже подключена к этому серверу |
0xF9 | Нет места в таблице номеров каналов рабочей станции |
0xFA | Нет места в таблице номеров каналов сервера |
0xFC | Сервера с указанным именем нет в сети |
0xFE | База объектов сервера заблокирована |
0xFF | Сервер не отвечает на запрос |
Для уничтожения канала вы можете использовать функцию DetachFromFileServer(): void DetachFromFileServer(WORD ConnectionID);
В качестве параметра вы должны передать функции номер канала, распределенного серверу, от которого вы собираетесь отключиться.
Таким образом, все, что вам нужно знать для создания канала с файл-серве-
ром, - это имя файл-сервера. Пользователь может ввести имя нужного файл-сервера, спросив его у супервизора. Однако вы можете предоставить пользователю меню активных файл-серверов. Для получения меню можно воспользоваться методикой обнаружения файл-серверов, изложенной нами ранее и основанной на протоколе SAP. Соответствующая программа, иллюстрирующая использование SAP-протокола, приведена дальше в разделе "Программа SLIST" этой главы.
Заметим, что сетевая оболочка сразу после своего запуска создает канал с ближайшим файл-сервером. Этот файл-сервер становится первичным (Primary).
Диски рабочей станции могут отображаться на каталоги файл-сервера. Если на рабочей станции текущим (т. е. используемым по умолчанию) является диск, отображенный на каталог файл-сервера, то этот файл-сервер называется текущим или используемым по умолчанию (Default).
Кроме того, существует понятие предпочтительного (Preferred) файл-сервера. Этот сервер должен быть задан явно специальной функцией.
Когда программа, запущенная на рабочей станции, обращается к файл-серверу, вначале проверяется, был ли задан предпочтительный файл-сервер. Если он задан не был, запрос адресуется текущему серверу. Если же текущий диск рабочей станции локальный (т. е. текущий сервер не определен), запрос адресуется первичному серверу.
В библиотеке NetWare C Interface есть несколько функций, позволяющих определить номера каналов первичного, текущего и предпочтительного сервера, задать предпочтительный сервер и изменить первичный сервер.
Функция GetPrimaryConnectionID() возвращает номер канала первичного сервера: WORD GetPrimaryConnectionID(void);
Функция GetDefaultConnectionID() возвращает номер канала для текущего сервера: WORD GetDefaultConnectionID(void);
Функция GetPreferredConnectionID() возвращает номер канала предпочтительного сервера или 0, если предпочтительный сервер не был задан.
Напомним, что номер канала соответствует индексу в таблице номеров каналов и лежит в пределах от 1 до 8.
Функция SetPreferredConnectionID() предназначена для определения предпочтительного сервера. Номер канала для сервера, который должен стать предпочтительным, передается функции в качестве параметра: void SetPreferredConnectionID(BYTE ConnectionID);
Если у вас нет библиотеки NetWare C Interface, вы можете создать канал с сервером или удалить его с помощью функции F1h прерывания INT21h.
Перед вызовом функции вам нужно загрузить регистры следующим образом:
На входе: | AH | = | F1h; |
AL | = | 0 - создать канал с файл-сервером, использовать номер канала, заданный в регистре DL; |
1 - отключить пользователя и удалить канал, номер которого задан в регистре DL;
2 - отключить пользователя от файл-сервера, номер канала которого задан в регистре DL;
При помощи функции F0h прерывания INT 21h вы сможете определить первичный и текущий сервер, а также задать новый первичный или предпочтительный сервер:
На входе: | AH | = | F0h; |
AL | = | 0 - установить предпочтительный файл-сервер, номер канала которого задан в регистре DL; |
1 - определить текущий предпочтительный сервер, номер сервера возвращается в регистре AL;
2 - получить в регистре AL номер текущего сервера;
4 - установить первичный файл-сервер, номер канала которого задан в регистре DL;
5 - получить в регистре AL номер первичного файл-сервера;
Подключение к файл-серверу
Создав канал с файл-сервером, программа еще не получила доступ к томам сервера и другому сервису. Следующим после создания канала этапом должно быть подключение пользователя к файл-серверу.
Для подключения пользователя к файл-серверу вы должны использовать функцию LoginToFileServer() из библиотеки Novell NetWare C Interface: int LoginToFileServer(char *ObjectName, WORD ObjectType, char *ObjectPassword);
Первый параметр ObjectName - указатель на имя пользователя, под которым его зарегистрировал супервизор сети или руководитель группы. Второй параметр определяет тип объекта. Для пользователя вы долны задать значение 1. Последний параметр - указатель на текстовую строку, содержащую пароль пользователя. Учтите, что и имя пользователя, и его пароль должны задаваться заглавными буквами.
Функция LoginToFileServer() выполняет достаточно сложную процедуру шифровки пароля, поэтому без использования библиотеки NetWare C Interface или аналогичных средств вы только с большим трудом сможете выполнить процедуру подключения к серверу без этой функции. Кстати, в отличие от других функций, исходный текст функции LoginToFileServer() и некоторых других не входит в комплект поставки библиотеки NetWare C Interface.
Есть функции и для отключения пользователя от одного или сразу ото всех файл-серверов.
С помощью функции Logout() вы можете отключиться сразу ото всех файл-серверов: void Logout(void);
Функция LogoutFromFileServer() предназначена для отключения только от одного сервера, номер канала которого задается в качестве единственного параметра функции: void LogoutFromFileServer(WORD ConnectionID);
В разделе "Программа LOG" мы приведем программу, которая умеет подключать пользователя к файл-серверу, а сейчас займемся тем, что определим список активных файл-серверов.
Пограмма LOG
2.4.2. Пограмма LOG
В этом разделе мы приведем исходный текст программы, выполняющей подключение пользователя к файл-серверу. Возможности этой программы ограничены по сравнению со стандартной утилитой login.exe: она, например, не выполняет интерпретацию файлов Login Script и System Login Script. После подключения к файл-серверу диск "S:" рабочей станции отображается на том SYS:. Вы можете использовать нашу программу как прототип собственной процедуры подключения к файл-серверу.
После проверки присутствия сетевой оболочки программа LOG с помощью функции GetConnectionNumber() получает номер канала текущего файл-сервера и затем, вызвав функцию GetFileServerName(), определяет имя текущего файл-сервера. Имя и номер канала текущего сервера выводятся в стандартный поток вывода.
Далее программа запрашивает имя сервера, имя пользователя и его пароль, при помощи функции AttachToFileServer() создает канал с указанным файл-сервером. Если канал уже есть или его удалось создать, новый сервер делается предпочтительным, для чего вызывается функция SetPreferredConnectionID().
Затем вызывается функция LoginToFileServer(). Она пытается подключить пользователя к предпочтительному серверу.
После подключения программа с помощью функции CheckConsolePrivileges() проверяет, имеет ли данный пользователь права оператора консоли, и выводит соответствующее сообщение.
Для того чтобы получить информацию о сервере, к которому только что подключился пользователь, программа LOG вызывает функцию GetFileServerDescriptionStrings(), которая записывает в четыре буфера имя фирмы-изготовителя, изменения и дату изменений, права на сетевую операционную систему. Содержимое всех этих буферов выводится в стандартный поток вывода.
Затем вызывается функция GetServerInformation(). С ее помощью определяется максимальное количество пользователей для данного сервера.
Так как мы только что подключились к файл-серверу, он должен стать первичным, поэтому на следующем шаге программа LOG вызывает функцию SetPrimaryConnectionID() и делает новый сервер первичным.
Подключившись к файл-серверу, вы еще не имеете доступа к его томам. Для того чтобы вы могли работать с дисками файл-сервера, вам необходимо отобразить один или несколько локальных дисков на сетевые каталоги. В нашей программе мы отображаем диск "S:" на корневой каталог тома SYS: нового первичного сервера. Для этого мы вызываем функцию AllocPermanentDirectoryHandle(). Эту функцию, а также все, что связано с дисками сервера, мы рассмотрим в следующей главе. // ===================================================
Приведенная программа составлена на языке
Листинг 1
Программа для обнаружения сетевой // оболочки и определения ее версии // Файл version\version.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
extern "C" int GetNetWareShellVersion(char *,char *, char *);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } printf("\nВерсия оболочки NetWare: %d.%d.%d\n",MajorVersion, MinorVersion, Revision);
} Приведенная программа составлена на языке программирования С++, поэтому внешняя функция GetNetWareShellVersion() должна быть описана как внешняя функция, использующая соглашения об именах и передаче параметров в стандарте С: extern "C" int GetNetWareShellVersion(char *,char *, char *);
Если бы программа была составлена на языке С, можно было бы использовать описание этой функции, приведенное в одном из include-файлов библиотеки Netware C Interface. Для включения всех include-файлов библиотеки Netware C Interface вы должны добавить в вашу программу следующую строку: #include <nit.h>
Для программ, составленных на языке С++, вам придется создавать собственные include-файлы на базе поставляемых вместе с библиотекой Netware C Interface.
Если у вас нет библиотеки Netware C Interface, вы можете узнать номер версии, вызвав непосредственно функцию 0xEA01 прерывания INT21h.
Перед вызовом функции вам нужно соответствующим образом загрузить регистры:
На входе: | AX | = | EA01h. |
ES:DI | = | Указатель на буфер размером 40 байт, в который будет записано текстовое описание среды рабочей станции. Это описание состоит из четырех строк: |
- название операционной системы;
- версия операционной системы;
- модель компьютера;
- фирма-производитель компьютера.
Последняя текстовая строка в буфере закрыта двумя двоичными нулями.
Приведем вариант предыдущей программы, не использующий библиотеку NetWare C Interface (листинг 2). Кроме версии сетевой оболочки программа выводит содержимое буфера с текстовым описанием среды рабочей станции. // ================================================================
Листинг 2
Программа для обнаружения сетевой оболочки, определе- // ния ее версии и вывода строк описания среды рабочей станции // Файл version1\version1.cpp // // (C) A. Frolov, 1993 // ================================================================ #include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include <string.h>
void PrintBuffer(char*);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; char Buffer[40]; union REGS regs; struct SREGS sregs; regs.x.ax = 0xea01; regs.x.di = FP_OFF(Buffer);
sregs.es = FP_SEG(Buffer);
intdosx(®s, ®s, &sregs);
MajorVersion = regs.h.bh; MinorVersion = regs.h.bl; Revision = regs.h.cl; printf("\nВерсия оболочки NetWare: %d.%d.%d\n",MajorVersion, MinorVersion, Revision);
printf("\nСтроки описания среды: ");
PrintBuffer(Buffer);
} void PrintBuffer(char *Buffer) { char *ptr; for(ptr = Buffer; *ptr != '\0';) { printf("'%s' ", ptr);
ptr = ptr + strlen(ptr) + 1; } }
Листинг 3
Просмотр списка активных серверов и вывод в стандарт- // ный поток имен и другой информации об активных серверах // Файл slist!\slist.cpp // // (C) A. Frolov, 1993 // ================================================================ #include <stdlib.h>
#include <stdio.h>
#include <mem.h>
#include <string.h>
#include <dos.h>
#include <direct.h>
#include "sap.hpp" void main(void) { SLIST *ServerList; int ccode = 0; printf("\n*SLIST!*, v.1.0, (C) Фролов А.В., 1993\n");
// Создаем объект класса SLIST. Конструктор этого объекта // получает всю необходимую информацию о серверах и // записывает ее в область данных объекта ServerList = new SLIST(GENERAL_SERVICE);
// Если при создании объекта были ошибки, завершаем // выполнение программы ccode = ServerList->
Error();
if(ccode) { printf("Ошибка %d\n", ccode);
return; } // Выводим список серверов printf("\nОбнаружены серверы:\n");
printf( "---------------------------------------------" "------------------------------\n");
ServerList->
PrintServersName();
printf( "---------------------------------------------" "------------------------------\n");
} Файл slist.cpp содержит определения функций-членов класса SLIST (листинг 4).
Конструктор SLIST() проверяет наличие сетевой оболочки, проверяет и запоминает тип запроса (получить сведения о ближайшем сервере или о всех серверах сети) и запоминает его. Затем конструктор инициализирует драйвер протокола IPX и открывает динамический короткоживущий сокет для работы с протоколом SAP. Далее в цикле создаются блоки ECB и ставятся в очередь на прием пакетов. Эти блоки ECB будут использованы для приема SAP-пакетов. После подготовки ECB конструктор посылает пакет запроса, ожидает примерно одну секунду и при помощи функций SLIST::GetServersName() и SLIST::GetServersInfo() получает и запоминает имена серверов и другую информацию.
Для работы с IPX-пакетами мы использовали функции из библиотеки NetWare C Interface. Назначение этих функций вам будет понятно из их названия, если вы прочитали предыдущий том "Библиотеки системного программиста".
Функция IPXInitialize() проверяет наличие драйвера протокола IPX и выполняет все инициализирующие действия, необходимые для использования протокола IPX.
Функция IPXOpenSocket() предназначена для открытия сокета. Первый параметр функции - указатель на переменную типа WORD, содержащую значение открываемого сокета или ноль, если надо получить динамический сокет. Байты в этой переменной расположены в обратном порядке, т. е. старший байт расположен по младшему адресу. Второй параметр функции IPXOpenSocket() определяет тип открываемого сокета - долгоживущий или короткоживущий. В нашем случае мы открываем динамический короткоживущий сокет.
После открытия сокета конструктор с помощью функции SLIST::ReceiveSAPPacket() подготавливает массив блоков ECB для приема ответных пакетов и, вызывая функцию IPXListenForPacket(), ставит эти блоки в очередь на прием. Функция IPXListenForPacket() имеет в качестве единственного параметра указатель на блок ECB.
Далее конструктор вызывает функцию SLIST::SendSAPPacket(), которая подготавливает блок ECB и заголовок IPX-пакета для SAP-запроса. При этом с помощью функции IPXGetInternetworkAddress() программа определяет свой собственный сетевой адрес. Функция IPXGetInternetworkAddress() имеет в качестве параметра указатель на структуру, в которую будет записан номер сети и сетевой адрес узла в сети.
Подготовив пакет, функция SLIST::SendSAPPacket() ставит его в очередь на передачу при помощи функции IPXSendPacket(), передавая ей в качестве параметра указатель на соответствующий блок ECB.
Когда пакет будет передан, конструктор ждет примерно одну секунду. В течение этого времени приходят ответные пакеты от серверов. После ожидания вызываются функции SLIST::GetServersName() и SLIST::GetServersInfo(), получающие соответсвенно имена серверов и дополнительную информацию.
Функция SLIST::GetServersName() переписывает имена откликнувшихся на запрос серверов из принятых SAP-пакетов во внутренний массив объекта класса SLIST.
Функция SLIST::GetServersInfo() выполняет более сложные действия.
Вначале с помощью функций GetPrimaryConnectionID() и GetDefaultConnectionID() она получает номера каналов первичного и текущего серверов, записывая их во внутренние переменные объекта класса SLIST. Затем запускается цикл по всем обнаруженным в сети серверам.
Внутри этого цикла для каждого сервера функция получает его номер канала при помощи функции GetConnectionID(). Если канала нет, рабочая станция создает его, подключаясь к серверу. Для подключения используется функция AttachToFileServer().
Затем сервер делается предпочтительным, для чего вызывается функция SetPreferredConnectionID(). Теперь все запросы будут идти к предпочтительному серверу. Внутри цикла мы по очереди будем делать все имеющиеся серверы предпочтительными и, направляя запросы, получать от серверов интересующую нас информацию.
Далее функция SLIST::GetServersInfo() вызывает функцию GetServerInformation(), которая записывает сведения о сервере в структуру ServerInfo. Первый параметр функции GetServerInformation() задает размер этой структуры, а второй является указателем на нее.
Перед возвратом функция SLIST::GetServersInfo() пытается получить серийный номер операционной системы Novell NetWare, работающей на предпочтительном файл-сервере, вызывая функцию GetNetworkSerialNumber(). Этой функции в качестве первого параметра необходимо передать указатель на переменную типа long, в качестве второго - указатель на переменную типа WORD. В первую переменную функция запишет серийный номер операционной системы, во вторую - серийный номер приложения, работающего на файл-сервере.
Надо заметить, что данная функция возвращает серийный номер только для тех серверов, к которым было выполнено подключение пользователя функцией LoginToFileServer(). Поэтому перед вызовом функции GetNetworkSerialNumber() мы записываем в поле серийного номера и номера приложения нулевое значение. Если содержимое этих полей останется нулевым, значит, пользователь не подключился к данному файл-серверу. Для сокращения размера листинга мы не проверяем код ошибки, возвращаемый функцией GetNetworkSerialNumber().
Функция SLIST::PrintServersName() в цикле для всех обнаруженных серверов выводит в стандартный поток вывода имя сервера, напротив которого указывается, является ли он первичным (Primary) или текущим (Default). Затем выводится версия Novell NetWare, взятая из полей netwareVersion и netwareSubVersion структуры ServerInfo. Для подключенных серверов выводится серийный номер и номер приложения.
Далее для всех серверов выводится номер канала, используемого сервером и записанного ранее в массив ConnID[].
После этого для каждого сервера выводится содержимое полей maxConnectionsSupported и connectionsInUse структуры ServerInfo, которые содержат максимальное количество каналов для сервера и количество каналов, используемых в данный момент.
Перед окончанием работы программы вызывается деструктор, который отменяет все ожидающие приема блоки ECB и закрывает динамический сокет. Для отмены блоков ECB используется функция IPXCancelEvent(), которой в качестве параметра передается указатель на отменяемый блок ECB. Сокет закрывается при помощи функции IPXCloseSocket(). Номер закрываемого сокета передается этой функции в качестве параметра. // ===================================================
Листинг 4
Функции для программы SLIST.CPP // Файл slist!\sap.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <mem.h>
#include <string.h>
#include <dos.h>
#include "sap.hpp" // ==================================================== // Конструктор класса SLIST // ==================================================== SLIST::SLIST(int ServiceType) { // Проверяем наличие сетевой оболочки и определяем ее версию MajorVersion = 0; asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si // Если оболочка не загружена, завершаем работу // программы с сообщением об ошибке if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
errno = 0xff; return; } // Проверяем тип SAP-запроса if (ServiceType != 1 && ServiceType != 3) { errno = NOT_SUPPORTED; return; } // Запоминаем тип запроса QueryType = ServiceType; // Инициализируем драйвер протокола IPX IPXInitialize();
// Открываем короткоживущий динамический сокет SrcSocket = 0x00; errno = IPXOpenSocket(&SrcSocket, SHORT_LIVED);
// Заполняем таблицу имен серверов нулями memset(ServerName,0,sizeof(ServerName));
// Подготавливаем блоки ECB для приема // пакетов от SAP-протокола for(int i=0;i<MAX_SERVERS;i++) { // Заполняем блок ECB ReceiveSAPPacket(&Query[i]);
// Ставим в очередь на прием пакета IPXListenForPacket(&Query[i].theECB);
} // Если не было ошибок, посылаем запрос if (!errno) { SendSAPPacket();
// Ждем примерно одну секунду sleep(1);
// Переписываем имена серверов и другую информацию GetServersName();
GetServersInfo();
} } // ==================================================== // Деструктор класса SLIST // ==================================================== SLIST::~SLIST() { // Отменяем ожидающие блоки ECB for(int i=0;i<MAX_SERVERS;i++) { IPXCancelEvent(&Query[i].theECB);
} // Закрываем сокет IPXCloseSocket(SrcSocket);
} // ==================================================== // Посылка SAP-запроса // ==================================================== void SLIST::SendSAPPacket(void) { // Сбрасываем поле inUseFlag и ESRAddress, устанавливаем тип пакета 0 SendPacket.theECB.inUseFlag = 0; SendPacket.theECB.ESRAddress = 0; SendPacket.SAPq.packetType = 0; // SAP-пакет состоит из одного фрагмента. Записываем в ECB // количество фрагментов, адрес и размер буфера SendPacket.theECB.fragmentCount = 1; SendPacket.theECB.fragmentDescriptor[0].address = &SendPacket.SAPq; SendPacket.theECB.fragmentDescriptor[0].size = sizeof(SAPQueryPacket);
// Записываем в ECB номер своего сокета SendPacket.theECB.socketNumber = SrcSocket; // Устанавливаем адрес назначения - все станции в текущей сети, // сокет SAP_SOCKET. Устанавливаем поле непосредственного адреса memset(SendPacket.SAPq.destination.network, '\x00', 4);
memset(SendPacket.SAPq.destination.node, '\xFF', 6);
SendPacket.SAPq.destination.socket = IntSwap(SAP_SOCKET);
memset(SendPacket.theECB.immediateAddress, '\xFF', 6);
// Устанавливаем свой адрес в заголовке запроса IPXGetInternetworkAddress(SendPacket.SAPq.source.network);
SendPacket.SAPq.source.socket = IntSwap(SrcSocket);
// Заполняем передаваемый пакет. Устанавливаем тип запроса // и тип сервера SendPacket.SAPq.queryType = IntSwap(QueryType);
SendPacket.SAPq.serverType = IntSwap(0x0004);
// Посылаем SAP-пакет IPXSendPacket(&SendPacket.theECB);
// Ожидаем завершения процесса передачи пакета while (SendPacket.theECB.inUseFlag) IPXRelinquishControl();
// Сохраняем код возврата errno = SendPacket.theECB.completionCode; } // ==================================================== // Прием SAP-пакетов // ==================================================== void SLIST::ReceiveSAPPacket(RECEIVE_PACKET *Query) { // Сбрасываем поле inUseFlag и ESRAddress Query->
theECB.inUseFlag = 0; Query->
theECB.ESRAddress = 0; // Записываем в ECB количество фрагментов, адрес и размер буфера Query->
theECB.fragmentCount = 1; Query->
theECB.fragmentDescriptor[0].address = &Query->
SB; Query->
theECB.fragmentDescriptor[0].size = sizeof(Query->
SB);
// Устанавливаем в ECB свой номер сокета Query->
theECB.socketNumber = SrcSocket; } // ==================================================== // Процедура переписывает имена серверов из тех // блоков ECB, для которых пришли пакеты // ==================================================== void SLIST::GetServersName(void) { for(int i=0,j=0; i<MAX_SERVERS; i++) { if(!Query[i].theECB.inUseFlag) { strcpy(ServerName[j],Query[i].SB.ServerName);
j++; } } } // ==================================================== // Процедура получает информацию о серверах // ==================================================== void SLIST::GetServersInfo(void) { // Получаем номера каналов первичного сервера // и сервера по умолчанию PrimaryConnID = GetPrimaryConnectionID();
DefaultConnID = GetDefaultConnectionID();
// Цикл по всем обнаруженным в сети активным серверам for(int i=0; i<MAX_SERVERS; i++) { if(ServerName[i][0]) { // Получаем номер канала сервера errno = GetConnectionID(ServerName[i], &ConnID[i]);
// Если канала нет, создаем его, подключаясь к серверу if(errno) { AttachToFileServer(ServerName[i], &ConnID[i]);
} // Делаем текущий сервер предпочтительным, так как // именно к нему должны поступать запросы errno = SetPreferredConnectionID(ConnID[i]);
// Получаем информацию о текущем сервере if(!errno) errno = GetServerInformation(sizeof(ServerInfo[i]), &ServerInfo[i]);
// Получаем серийный номер и номер приложения SerialNumber[i]=ApplicationNumber[i]=0L; errno = GetNetworkSerialNumber(&SerialNumber[i], &ApplicationNumber[i]);
errno = 0; } } } // ============================================================ // Процедура распечатывает имена и другую информацию о серверах // ============================================================ void SLIST::PrintServersName(void) { // Цикл по всем обнаруженным в сети активным серверам for(int i=0; i<MAX_SERVERS; i++) { if(ServerName[i][0]) { // Выводим имя сервера printf("%s",ServerInfo[i].serverName);
// Если номер канала текущего сервера совпадает с // номером канала первичного сервера, выводим строку "\t[Primary]" if(ConnID[i] == PrimaryConnID) printf("\t[Primary]");
else printf("\t[ ]");
// Если номер канала текущего сервера совпадает с // номером канала сервера по умолчанию, выводим строку " [Default]" if(ConnID[i] == DefaultConnID) printf(" [Default]");
else printf(" [ ]");
// Выводим версию сетевой операционной системы, // работающей на текущем сервере printf(" v.%d.%d, ", ServerInfo[i].netwareVersion, ServerInfo[i].netwareSubVersion);
// Для подключенных серверов выводим серийный // номер и номер приложения if(SerialNumber[i] != 0L) printf("s/n %08.8lX/%04.4X", SerialNumber[i], ApplicationNumber[i]);
else printf("- Not Logged In -");
// Выводим номер канала, используемого для связи с текущим сервером printf("\tConnID: %d,",ConnID[i]);
// Выводим максимальное число каналов, поддерживаемых // сервером, и количество используемых каналов printf(" (%d-%d)\n", ServerInfo[i].maxConnectionsSupported, ServerInfo[i].connectionsInUse);
} } } Файл sap.hpp содержит все определения констант и описания структур, необходимые для программы SLIST. В частности, в этом файле описан класс SLIST. // ===================================================
Листинг 5
Include-файл для программы SLIST.CPP // Файл slist!\sap.hpp // // (C) A. Frolov, 1993 // =================================================== // Максимальное количество серверов, для которых выполняется опрос #define MAX_SERVERS 8 // Типы сервиса SAP #define GENERAL_SERVICE 1 #define NEAREST_SERVICE 3 #define NOT_SUPPORTED 1 // Короткоживущий сокет #define SHORT_LIVED 0x00 // Сокет для SAP-протокола #define SAP_SOCKET 0x452 // Тип пакета SAP #define SAP_PACKET_TYPE 2 // Определения используемых типов данных #define BYTE unsigned char #define WORD unsigned short // Сетевой адрес typedef struct IPXAddress { BYTE network[4]; BYTE node[6]; WORD socket; } IPXAddress; // Заголовок IPX-пакета typedef struct IPXHeader { WORD checkSum; WORD length; BYTE transportControl; BYTE packetType; IPXAddress destination; IPXAddress source; } IPXHeader; // Заголовок SAP-пакета typedef struct SAPHeader { WORD checksum; WORD length; BYTE transportControl; BYTE packetType; IPXAddress destination; IPXAddress source; WORD SAPPacketType; WORD serverType; BYTE serverName[48]; IPXAddress serverAddress; WORD interveningNetworks; } SAPHeader; // Пакет для посылки SAP-запроса typedef struct SAPQueryPacket { WORD checksum; WORD length; BYTE transportControl; BYTE packetType; IPXAddress destination; IPXAddress source; WORD queryType; WORD serverType; } SAPQueryPacket; // Структуры для описания блока ECB typedef struct ECBFragment { void far *address; WORD size; } ECBFragment; typedef struct ECB { void far *linkAddress; void (far *ESRAddress)();
BYTE inUseFlag; BYTE completionCode; WORD socketNumber; BYTE IPXWorkspace[4]; BYTE driverWorkspace[12]; BYTE immediateAddress[6]; WORD fragmentCount; ECBFragment fragmentDescriptor[2]; } ECB; // SAP-пакет typedef struct { IPXHeader Header; WORD ResponseType; WORD ServerType; BYTE ServerName[48]; BYTE Network[4]; BYTE Node[6]; WORD Socket; WORD InterveningNetworks; } SAP; // Структура для передачи SAP-пакета typedef struct { ECB theECB; SAPQueryPacket SAPq; } SEND_PACKET; // Структура для приема SAP-пакета typedef struct { ECB theECB; SAP SB; } RECEIVE_PACKET; // Информация о файл-сервере typedef struct { char serverName[48]; BYTE netwareVersion; BYTE netwareSubVersion; WORD maxConnectionsSupported; WORD connectionsInUse; WORD maxVolumesSupported; BYTE revisionLevel; BYTE SFTLevel; BYTE TTSLevel; WORD peakConnectionsUsed; BYTE accountingVersion; BYTE VAPversion; BYTE queingVersion; BYTE printServerVersion; BYTE virtualConsoleVersion; BYTE securityRestrictionLevel; BYTE internetBridgeSupport; } FILE_SERV_INFO; // Описания функций библиотеки NetWare C Interface extern "C" int IPXInitialize(void);
extern "C" int IPXOpenSocket(WORD *, BYTE);
extern "C" int IPXListenForPacket(ECB *);
extern "C" int IPXCancelEvent(ECB *);
extern "C" int IPXCloseSocket(WORD);
extern "C" WORD IntSwap(WORD);
extern "C" void IPXGetInternetworkAddress(BYTE *);
extern "C" void IPXSendPacket(ECB *);
extern "C" void IPXRelinquishControl(void);
extern "C" IPXGetLocalTarget(BYTE *, BYTE *, int*);
extern "C" WORD IPXGetIntervalMarker(void);
extern "C" long LongSwap(long);
extern "C" int AttachToFileServer(char *, WORD *);
extern "C" int SetPrimaryConnectionID(int);
extern "C" int GetServerInformation(int, FILE_SERV_INFO *);
extern "C" WORD GetPreferredConnectionID(void);
extern "C" WORD GetPrimaryConnectionID(void);
extern "C" WORD GetDefaultConnectionID(void);
extern "C" int SetPreferredConnectionID(WORD);
extern "C" int GetConnectionID(char *, WORD *);
extern "C" void DetachFromFileServer(WORD);
extern "C" int GetNetWareShellVersion(BYTE *,BYTE *, BYTE *);
extern "C" int IsConnectionIDInUse(WORD);
extern "C" int GetNetworkSerialNumber(long *, int*);
// Класс SLIST class SLIST { private: WORD QueryType; // тип запроса WORD SrcSocket; // сокет // Массив для приема SAP-пакетов RECEIVE_PACKET Query[MAX_SERVERS]; // Передаваемый SAP-пакет SEND_PACKET SendPacket; // Таблицы имен файл-серверов, серийных // номеров и номеров приложений char ServerName[MAX_SERVERS][48]; long SerialNumber[MAX_SERVERS]; int ApplicationNumber[MAX_SERVERS]; // Таблица информации о файл-серверах FILE_SERV_INFO ServerInfo[MAX_SERVERS]; // Таблица номеров каналов файл-серверов WORD ConnID[MAX_SERVERS]; // Функции для приема и передачи SAP-пакетов void ReceiveSAPPacket(RECEIVE_PACKET *Query);
void SendSAPPacket(void);
// Функции для получения имен файл-серверов и // другой информации о файл-серверах void GetServersName(void);
void GetServersInfo(void);
public: int errno; // код ошибки WORD PreferredConnID; // предпочтительный сервер WORD PrimaryConnID; // первичный сервер WORD DefaultConnID; // сервер по умолчанию BYTE MajorVersion; // верхний номер версии BYTE MinorVersion; // нижний номер версии BYTE Revision; // номер изменений SLIST(int);
// конструктор ~SLIST();
// деструктор // Функция для вывода имен серверов void PrintServersName(void);
// Проверка ошибок int Error(void) { return errno; } };
Листинг 6
Подключение к серверу // Файл log\log.c // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "nit.h" // include-файлы из библиоткеи #include "niterror.h" // NetWare C Interface // Эта функция не описана в include-файлах // библиотеки NetWare C Interface, поэтому опишем ее сами. void GetServerInformation(int, FILE_SERV_INFO*);
void main(void) { int ccode; char ServerName[48]; char UserName[48]; char Password[128]; WORD ConnID, ConnNumber; char companyName[80], revision[80]; char revisionDate[24], copyrightNotice[80]; FILE_SERV_INFO serverInfo; BYTE newDirectoryHandle, effectiveRightsMask; char driveLetter; char MajorVersion=0; char MinorVersion=0; char Revision=0; printf("NetWare Login, (C) Фролов А.В., 1993\n");
asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Получаем номер канала, используемого сервером // по умолчанию (default) для связи с рабочей станцией, на // которой была запущена эта программа ConnNumber = GetConnectionNumber();
// Получаем имя файл-сервера, используемого по умолчанию (default) GetFileServerName(0, ServerName);
// Выводим имя и номер канала для // сервера, используемого по умолчанию if(ConnNumber) printf("Сервер по умолчанию '%s', ConnNumber=%04.4X\n", ServerName, ConnNumber);
// Вводим имя сервера, имя пользователя и пароль. // Преобразуем все введенные буквы в заглавные. printf("\nВведите имя сервера: ");
gets(ServerName);
strupr(ServerName);
printf("\nВведите ваше имя: ");
gets(UserName);
strupr(UserName);
printf("\nВведите пароль: ");
gets(Password);
strupr(Password);
// Создаем канал с сервером ccode = AttachToFileServer(ServerName, &ConnID);
// Если канал удалось создать или он уже был создан раньше, // выводим имя сервера и номер канала, используемого // на рабочей станции для идентификации сервера. if(ccode == 0 || ccode == ALREADY_ATTACHED_TO_SERVER) { printf("\nServerName='%s', ServerID=%04.4X", ServerName, ConnID);
// Делаем данный сервер предпочтительным для того, // чтобы все запросы направлялись к нему в первую очередь SetPreferredConnectionID(ConnID);
// Подключаем пользователя к файл-серверу ccode = LoginToFileServer(UserName,OT_USER,Password);
if(!ccode) { // Если подключение произошло успешно, проверяем, есть ли // у подключившегося пользователя права оператора консоли if(!CheckConsolePrivileges()) printf("Вы оператор консоли\n");
// Получаем строки описания сервера и выводим их // в стандартный поток GetFileServerDescriptionStrings(companyName, revision, revisionDate, copyrightNotice);
printf("Описание сервера:\n%s\n%s\n\n%s\n%s\n", companyName,revision, revisionDate, copyrightNotice);
// Получаем информацию о сервере, выводим максимальное количество // пользователей, которые могут подключиться к // данному файл-серверу. GetServerInformation(sizeof(serverInfo), &serverInfo);
printf("Версия на %d пользователей\n", serverInfo.maxConnectionsSupported);
// Делаем данный сервер первичным. SetPrimaryConnectionID(ConnID);
// Отображаем диск S: рабочей станции на // корневой каталог тома SYS: сервера driveLetter = 'S'; ccode = AllocPermanentDirectoryHandle(0,"SYS:\\", driveLetter, &newDirectoryHandle,&effectiveRightsMask);
printf("Диск отображен, код CCode = %d\n",ccode);
} } else { printf("Ошибка при подключении: %04.4X\n",ccode);
return; } }
Таблица томов файл-сервера
3.1. Таблица томов файл-сервера
3.2. Отображение дисков рабочей станции
3.3. Таблица каталогов файл-сервера
3.4. Создание, переименование и удаление каталогов
3.5. Просмотр и изменение атрибутов
В этой главе мы рассмотрим вопросы, связанные с обращением к томам и каталогам, расположенным на файл-сервере Novell NetWare. Мы расскажем вам о механизме отображения дисков рабочей станции на сетевые каталоги и научим вас составлять программы, выполняющие такое отображение. Вы узнаете об атрибутах каталогов, сможете создавать, переименовывать и удалять каталоги, узнавать и изменять их атрибуты.
Таблица томов файл-сервераПрограмма VOLINFO
Листинг 7
Отображение дисков рабочей станции
Программа WKSTABLE
Программа DIRMAP
Программа DIRUNMAP
Таблица каталогов файл-сервера
Программа DIRSCAN
Создание, переименование и удаление каталогов
Программы MAKEDIR, RENMDIR, DELDIR
Просмотр и изменение атрибутов
Программа GETMASK
Программа DIRSCAN
3.3.1. Программа DIRSCAN
С помощью программы DIRSCAN вы сможете получить список подкаталогов для каталога, путь к которому задан в качестве параметра при запуске программы.
Для преобразования идентификатора пользователя, создавшего каталог,
в имя мы использовали функцию GetBinderyObjectName() из библиотеки NetWare C Interface: int GetBinderyObjectName(long ObjectID, char *ObjectName, WORD *ObjectType);
Функция ищет в базе объектов запись с идентификатором ObjectID и в случае успеха записывает в переменные, адресуемые параметрами ObjectName и ObjectType, имя и тип объекта. В случае успешного поиска функция возвращает нулевое значение.
Итак, исходный текст программы DIRSCAN: // ===================================================
Программа GETMASK
3.5.1. Программа GETMASK
Программа GETMASK (листинг 15) показывает байт маски прав доступа для каталога, путь к которому задан в качестве параметра при запуске программы. // ===================================================
Программа VOLINFO
3.1.1. Программа VOLINFO
Приведем исходный текст программы, которая выводит список смонтированных томов для текущего сервера. Если текущий диск локальный, программа выходит информацию о первичном сервере.
Программа в цикле с помощью функции GetVolumeName() получает имена смонтированных томов. Если первый байт имени тома содержит двоичный ноль, программа выходит из цикла.
Для каждого тома программа получает информацию о томе, вызывая функцию GetVolumeInfoWithNumber(). // ===================================================
Программа WKSTABLE
3.2.1. Программа WKSTABLE
Приведем программу, отображающую состояние внутренних таблиц сетевой оболочки. Кроме таблиц отображения дисковых устройств программа показывает содержимое таблицы номеров каналов и таблицы имен файл-серверов, с которыми рабочая станция создала каналы.
Программа WKSTABLE (листинг 8) получает адреса всех таблиц и выводит их в стандартный поток вывода в соответствующем формате. Вы можете подключиться к нескольким серверам и выполнить отображение дисков утилитами attach.exe и map.exe из каталога SYS:PUBLIC, а затем запустить программу и посмотреть содержимое таблиц. // ============================================================
Программы MAKEDIR, RENMDIR, DELDIR
3.4.1. Программы MAKEDIR, RENMDIR, DELDIR
В этом разделе мы приведем исходные тексты программ, выполняющих основные действия над сетевыми каталогами - создание, переименование и удаление.
Программа MAKEDIR (листинг 12) создает каталог и задает для него маску прав доступа. Имя создаваемого каталога и маска передаются программе при запуске в качестве параметров: // ===================================================
Программа DIRMAP
3.2.2. Программа DIRMAP
При помощи программы DIRMAP (листинг 9) вы сможете отображать локальные диски рабочей станции на сетевые каталоги. В качестве первого параметра при запуске программы необходимо задать букву отображаемого диска, в качестве второго - полный путь к сетевому каталогу (без имени сервера). // ===================================================
Программа SETMASK
3.5.2. Программа SETMASK
Программа SETMASK (листинг 16) демонстрирует использование функции ModifyMaximumRightsMask() для изменения маски прав доступа существую-щего каталога.
В качестве первого параметра программе необходимо указать путь к каталогу. Второй и третий параметр задают соответственно удаляемые или добавляемые права доступа. // ===================================================
Таблица томов файл-сервера
Каждый файл-сервер хранит информацию о сетевых томах в таблице томов (Volume Table), состоящей из 256 элементов. Номера элементов используются для адресации томов и называются номерами томов (Volume Number).
Зная номер тома, программа может получить такие важные характеристики тома, как его объем, размер свободного пространства на томе, максимальное количество каталогов, которое можно создать на томе, количество уже созданных каталогов. Кроме того, программа может определить, является ли данный том файл-сервера съемным.
Одна из важных задач - определение имен и номеров томов, смонтированных на файл-сервере.
Для определения имен смонтированных томов лучше всего воспользоваться функцией GetVolumeName() из библиотеки NetWare C Interface: int GetVolumeName(int VolumeNumber, char*VolumeName);
Первый параметр функции задает номер тома, для которого необходимо получить имя. На сервере Novell NetWare версии 2.2 можно создать 32 тома, версия 3.11 допускает существование 64 томов. Поэтому диапазон возможных значений для первого параметра в зависимости от версии NetWare может быть от 0 до 31 или от 0 до 63.
Второй параметр - указатель на буфер размером 16 байт, в который будет записано имя тома.
В случае ошибки функция возвращает ненулевое значение. Например, если вы укажете недопустимый номер тома, функция возвратит значение 0x98.
Для определения списка смонтированных томов вы можете вызывать эту функцию в цикле, задавая ей номера томов в диапазоне от 0 до 63. Если функция вернет ненулевое значение или если на месте первой буквы имени тома будет двоичное нулевое значение, то это означает, что на данном сервере больше нет смонтированных томов.
Можно решить и обратную задачу - по имени тома определить его номер. Для этого предназначена функция GetVolumeNumber(): int GetVolumeNamber(char*VolumeName, int *VolumeNumber);
Для тома, имя которого задается первым параметром, функция определяет номер тома и записывает его по адресу, заданному вторым параметром. Если функция вернет ненулевое значение, указанному номеру тома не соответствует никакой том.
Для получения справочной информации о томе удобно воспользоваться функцией GetVolumeInfoWithNumber(): int GetVolumeInfoWithNumber(BYTE VolumeNumber, char *VolumeName, WORD *TotalBlocks, WORD *SectorsPerBlock, WORD *AvailableBlocks, WORD *TotalDirectorySlots, WORD *AvailableDirectorySlots, WORD *Removable);
Для тома, номер которого задан параметром VolumeNumber, функция возвращает имя, записывая его по адресу, указанному параметром VolumeName, общее количество блоков (параметр TotalBlocks), количество секторов в одном блоке (параметр SectorsPerBlock), количество свободных блоков (параметр AvailableBlocks), количество каталогов, имеющихся на томе (параметр TotalDirectorySlots), количество каталогов, которые можно дополнительно создать на томе (параметр AvailableDirectorySlots), признак того, что том является съемным (параметр Removable).
При использовании этой функции следует учесть, что она предоставляет информацию только о тех серверах, к которым пользователь подключен. Если рабочая станция создала канал с сервером, но не подключилась к нему, функция вернет код ошибки 252.
Размер сектора составляет 512 байт, что вы можете использовать для подсчета объема тома в килобайтах.
Если переменная, адрес которой указан параметром Removable, получила значение 0, это означает, что соответствующий том несъемный.
В следующем разделе мы приведем исходный текст программы, которая для текущего сервера (или первичного сервера, если текущий сервер не определен) выводит список смонтированных томов. Для каждого тома программа выводит его объем в килобайтах и размер имеющегося на томе свободного пространства.
Информация о томах может быть получена и без использования описанных выше функций библиотеки NetWare C Interface.
Для определения соответствия между номером тома и именем тома можно воспользоваться функцией E2h прерывания INT21h:
На входе: | AH | = | E2h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа; | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 6 BYTE VolumeNumber; // номер тома };
В этом буфере вам надо заполнить все поля, указав размер буфера и номер тома, для которого необходимо получить имя. Код функции в поле Function должен иметь значение 6.
Приведем формат буфера ответа: struct REPLAY { WORD PacketLength; // размер пакета BYTE VolumeNameLength; // длина имени тома BYTE VolumeName[16]; // имя тома };
Если указанному номеру тома не соответствует ни один том, поле VolumeNameLength будет содержать нулевое значение.
Для выполнения обратной операции - получения номера тома по его имени - можно воспользоваться той же функцией E2h прерывания INT 21h. Но формат буферов запроса и ответа будет другой.
Формат буфера запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 5 BYTE NameLength; // длина имени тома BYTE VolumeName[16]; // имя тома };
В этом буфере вам надо указать размер буфера, длину имени тома и имя тома, для которого необходимо получить номер имени. Код функции в поле Function должен иметь значение 5.
Приведем формат буфера ответа: struct REPLAY { WORD PacketLength; // размер пакета BYTE VolumeNumber; // номер имени тома };
Если том, имя которого указано в буфере запроса, смонтирован, регистр AL после возврата из функции будет равен нулю.
Для получения информации о смонтированном томе по номеру тома можно воспользоваться функцией DAh прерывания INT 21h:
На входе: | AH | = | DAh; |
DL | = | Номер тома; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер ответа имеет следующий формат: struct REPLAY { WORD SectorsPerBlock; WORD TotalBlocks; WORD AvailableBlocks; WORD TotalDirectorySlots; WORD AvailableDirectorySlots; BYTE VolumeName[16]; WORD Removable; };
Назначение полей этой структуры аналогично назначению параметров функции GetVolumeInfoWithNumber().
Отображение дисков рабочей станции
Для обеспечения возможности работы с файлами, расположенными на дисках файл-сервера, сетевая оболочка выполняет отображение локальных дисков рабочей станции на сетевые каталоги. При этом прикладная программа, запущенная на рабочей станции, может и не заметить, что она работает не с локальным диском, а с удаленным. Разумеется, для обеспечения возможности отображения пользователь должен подключиться к файл-серверу, сообщив ему свое имя и пароль. В предыдущей главе вы научились составлять программы, подключающие пользователя к файл-серверу.
Дополнительно к таблице номеров каналов серверов сетевая оболочка работает еще с тремя таблицами, необходимыми для отображения дисков. Это таблица флагов дисковых устройств (Drive Flag Table), таблица номеров каналов дисковых устройств (Drive Connection ID Table) и таблица индексов дисковых устройств (Drive Handle Table).
Эти таблицы могут отображать 32 дисковых устройства, все они имеют размер 32 байта, по одному байту на одно устройство.
У вас может возникнуть вопрос: почему 32 дисковых устройства, а не 26? Действительно, MS-DOS позволяет вам использовать только 26 дисковых устройств, обозначая их буквами в диапазоне от A до Z. Сетевая оболочка добавляет еще шесть устройств, которые обычно используются в качестве временных логических дисков, отображаемых на сетевые каталоги только на время работы программы. Для обозначения этих дополнительных дисков сетевая оболочка использует следующие символы:
[ | левая квадратная скобка; |
\ | обратный слеш; |
] | правая квадратная скобка; |
^ | символ caret (знак для вставки); |
_ | подчеркивание; |
' | апостроф. |
Таблица флагов дисковых устройств (Drive Flag Table) содержит байты состояния для каждого дискового устройства рабочей станции. Пользуясь этой таблицей, программа может определить, какие диски рабочей станции локальные, а какие отображены на сетевые каталоги. Приведем список возможных значений элементов таблицы флагов:
0 | Диска нет, т. е. этот диск не отображен ни на локальный диск, ни на удаленный сетевой каталог |
01h | Диск постоянно отображен на сетевой каталог |
02h | Диск временно отображен на сетевой каталог (временное отображение действует только во время работы программы; когда программа завершается, отображение автоматически отменяется) |
80h | Локальный диск рабочей станции |
81h | Локальный диск рабочей станции, постоянно отображенный на сетевой каталог |
82h | Локальный диск рабочей станции, временно отображенный на сетевой каталог |
Когда программа обращается к диску, сетевая оболочка просматривает таблицу флагов и определяет, с каким диском надо работать - с отображенным или локальным.
Для того чтобы определить, на каталог какого сервера отображен тот или иной диск, программа может использовать таблицу номеров каналов дисковых устройств (Drive Connection ID Table). В этой таблице для каждого диска указан номер канала, который сетевая оболочка рабочей станции использует для работы с сервером, на чей каталог отображен данный диск. Если диск локальный или его нет совсем, для такого диска в таблице находится нулевое значение. Так как рабочая станция может образовать до 8 каналов, таблица номеров каналов дисковых устройств может содержать значения от 1 до 8 (или 0 для неиспользуемых дисков).
Таблица индексов дисковых устройств (Drive Handle Table) имеет отношение к таблице каталогов, расположенной в файл-сервере. Для каждого канала файл-сервер заводит отдельную таблицу индексов дисковых устройств, в которой содержится информация о томе и каталоге, на который отображается соответствующий диск рабочей станции. Подробнее об этом мы расскажем ниже в разделе "Таблица каталогов файл-сервера".
Вы можете отобразить диск рабочей станции на сетевой каталог при помощи функции AllocPermanentDirectoryHandle(), которая создает новый элемент в таблице индексов каталога, расположенной на файл-сервере. Кроме того, эта функция изменяет содержимое всех таблиц сетевой оболочки, имеющих отношение к отображению дисков рабочей станции.
Приведем прототип функции AllocPermanentDirectoryHandle(): int AllocPermanentDirectoryHandle(BYTE DirectoryHandle, char *DirectoryPath, char DriveLetter, BYTE *NewDirectoryHandle, BYTE *EffectiveRightsMask);
Вы можете указать путь к отображаемому сетевому каталогу либо с помощью индекса каталога через параметр DirectoryHandle, либо через полный путь к каталогу (параметр DirectoryPath), либо используя комбинированный способ. Впрограмме DIRMAP, демонстрирующей применение этой функции (см. ниже), мы указали полный путь к отображаемому каталогу при помощи параметра DirectoryPath, а для параметра DirectoryHandle мы задали нулевое значение.
Параметр DriveLetter задает отображаемый диск. Если этот диск до вызова функции AllocPermanentDirectoryHandle() отображался на другой сетевой каталог или был локальным диском рабочей станции, это никак не скажется на успехе или результате отображения.
Параметр NewDirectoryHandle - указатель на переменную, в которую будет записан индекс, связанный с отображаемым каталогом. Вы можете использовать этот индекс для ссылки на каталог вместо полного имени каталога. Он будет нужен также для отмены отображения диска.
Параметр EffectiveRightsMask - указатель на байт памяти, в который будет записана маска прав доступа пользователя в данном каталоге. Назначение отдельных битов этой маски мы рассмотрим ниже в разделе "Таблица каталогов файл-сервера".
Отображение диска сохраняется до тех пор, пока оно не будет отменено функцией DeallocateDirectoryHandle(): int DeallocateDirectoryHandle(BYTE DirectoryHandle);
Эта функция отменяет отображение диска на каталог, имеющий индекс DirectoryHandle.
Для того чтобы отменить отображение диска, пользуясь его буквенным обозначением, можно воспользоваться функцией GetDirectoryHandle(), возвращающей индекс каталога для диска, заданного своим номером (0 - A, 1 - B и т. д.): DirectoryHandle = GetDirectoryHandle(argv[1][0] - 'A');
Для отображения локального диска на сетевой каталог вместо функции AllocPermanentDirectoryHandle() можно воспользоваться функцией E2h прерывания INT 21h:
На входе: | AH | = | E2h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 18 BYTE DirectoryHandle; // индекс каталога BYTE DriveLetter; // отображаемый диск BYTE PathLength; // длина пути к каталогу BYTE DirectoryPath[PathLength]; // путь к каталогу };
Приведем формат буфера ответа: struct REPLAY { WORD PacketLength; // размер пакета BYTE NewDirectoryHandle; // новый индекс каталога BYTE EffectiveRightsMask ; // маска прав доступа };
Для отмены отображения диска вместо функции DeallocateDirectoryHandle можно использовать функцию E2h прерывания INT 21h, заполнив буфер запроса следующим образом: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 20 BYTE DirectoryHandle; // индекс каталога };
До сих пор мы рассматривали так называемое постоянное отображение дисков на сетевые каталоги. Это отображение сохраняется до тех пор, пока оно не будет отменено вызовом соответствующей функции. Однако вы можете заказать временное отображение, которое будет отменено автоматически после завершения работы программы, создавшей временное отображение.
Временное отображение выполняется функцией AllocTemporaryDirectoryHandle(): int AllocTemporaryDirectoryHandle(BYTE DirectoryHandle, char *DirectoryPath, char DriveLetter, BYTE *NewDirectoryHandle, BYTE *EffectiveRightsMask);
Параметры этой функции полностью аналогичны параметрам функции AllocPermanentDirectoryHandle(). Дополнительно к дискам с именами A - Z вы можете использовать диски, обозначаемые следующими символами: [, \, ], ^, _, '.
Для временного отображения диска на сетевой каталог вы можете воспользоваться функцией E2h прерывания INT 21h. Вам надо заполнить буфер запроса аналогично тому, как это нужно для создания постоянного отображения, но в поле Function необходимо указать значение 19. Формат буфера ответа полностью аналогичен формату, используемому при постоянном отображении.
Для получения адресов таблиц можно воспользоваться функцией EFh прерывания INT 21h:
На входе: | AH | = | EFh; |
AL | = | 0 - получить адрес таблицы индексов дисковых устройств (Drive Handle Table); |
1 - получить адрес таблицы флагов дисковых устройств (Drive Flag Table);
2 - получить адрес таблицы номеров каналов дисковых устройств (Drive Connection ID Table);
3 - получить адрес таблицы номеров каналов
(Connection ID Table);
Таблица каталогов файл-сервера
Вся информация о содержимом каталогов файл-сервера находится в таблице каталогов. Таблица каталогов по своему назначению напоминает каталоги MS-DOS: в ней находится информация об именах каталогов и файлов, байты атрибутов, информация о дате создания, идентификатор пользователя, создавшего файл или каталог, информация о правах доступа и т. д.
Для просмотра каталогов в операционной системе MS-DOS имеются соответствующие функции, о чем мы рассказывали в первом томе "Библиотеки системного программиста". Разумеется, программа может просматривать сетевые каталоги с помощью этих функций. Однако если вы воспользуетесь функциями, специально предназначенными для просмотра содержимого каталогов, вы сможете узнать имя пользователя, создавшего каталог или файл, а также получить информацию о правах доступа к каталогу или файлу.
Сетевая оболочка Novell NetWare имеет две разные функции для поиска подкаталогов в каталоге и для поиска файлов в каталоге (MS-DOS получает информацию сразу и о файлах, и о подкаталогах). В этом разделе мы расскажем о поиске каталогов, а файлами займемся позже, в главе "Работа с файлами".
Библиотека NetWare C Interface содержит функцию ScanDirectoryInformation(), предназначенную для просмотра содержимого каталогов. С ее помощью вы можете получить информацию о подкаталогах любого сетевого каталога.
Приведем прототип функции: int ScanDirectoryInformation(BYTE DirectoryHandle, char *SearchDirectoryPath, int *SequenceNumber, char *DirectoryName, BYTE *CreationDateAndTime, long *OwnerObjectID, BYTE *MaximumRightsMask);
С помощью параметров DirectoryHandle и SearchDirectoryPath задается каталог, содержимое которого надо просмотреть. Параметр DirectoryHandle - это индекс в таблице дисков, которую файл-сервер поддерживает для каждой рабочей станции. Когда диск рабочей станции отображается на каталог файл-сервера, создается новый элемент в таблице дисков файл-сервера. Индекс DirectoryHandle однозначно соответствует сетевому каталогу, на который отображается локальный диск рабочей станции.
Вы можете задать в качестве параметра DirectoryHandle нулевое значение, при этом параметр SearchDirectoryPath должен указывать на текстовую строку, содержащую полный путь к исследуемому каталогу. В строке могут использоваться символы "*" и "?", которые трактуются так же, как и в MS-DOS. Например, для поиска всех подкаталогов каталога SYS:USERS вы можете задать 0 в поле DirectoryHandle и строку "SYS:USERS\*" в поле SearchDirectoryPath.
Имя файл-сервера не указывается, так как запрос направляется к предпочтительному, текущему или первичному серверу, в зависимости от того, существует ли предпочтительный сервер или текущий сервер. Если был задан предпочтительный сервер, функция ScanDirectoryInformation() обращается к предпочтительному серверу. Если предпочтительный сервер не был задан и на рабочей станции текущим является диск, отображенный на сетевой каталог, функция ScanDirectoryInformation() обращается к файл-серверу, содержащему этот каталог. Если текущий диск рабочей станции является локальным, функция ScanDirectoryInformation() обращается к первичному серверу.
Параметр SequenceNumber - указатель на слово, которое должно содержать нулевое значение при первом вызове функции ScanDirectoryInformation(). Для получения информации о всех подкаталогах вам придется вызывать эту функцию в цикле, при этом слово, на которое указывает параметр SequenceNumber, будет автоматически увеличиваться самой функцией.
Остальные четыре параметра - указатели на переменные, в которые будут записаны возвращаемые значения.
В область памяти, на которую указывает параметр DirectoryName, после успешного вызова функции будет записано имя обнаруженного подкаталога. Размер этой области памяти должен составлять 16 байт. Вызывая в цикле функцию ScanDirectoryInformation(), программа будет получать в поле, адресуемом параметром DirectoryName, имена найденных подкаталогов.
Параметр CreationDateAndTime указывает на область памяти, размером 4 байта, в которую будет записана информация о дате и времени создания найденного подкаталога. На Рисунок 1 приведен формат этой информации. К номеру года, возвращенному в первом байте, необходимо добавить число 80.
Рисунок 1. Формат даты и времени
Параметр OwnerObjectID - указатель на слово, в котором будет записан идентификатор пользователя, создавшего каталог. По этому идентификатору с помощью функции GetBinderyObjectName() вы легко сможете получить имя пользователя.
Параметр MaximumRightsMask - указатель на байт, в который будет записано значение маски прав доступа, связанное с данным каталогом. Маска используется для определения возможности доступа к каталогу и определяется при создании каталога. Каждый бит маски, установленный в 1, разрешает соответствующий вид доступа:
Номер бита | Доступ |
0 | Чтение файлов |
1 | Запись в файлы |
2 | Открытие файлов |
3 | Создание файлов |
4 | Удаление файлов |
5 | Можно создавать подкаталоги и задавать для создаваемых подкаталогов права доступа |
6 | Поиск файлов в каталоге |
7 | Изменение атрибутов файлов |
Функция ScanDirectoryInformation() при успешном завершении возвращает нулевое значение, в противном случае - код ошибки:
Код ошибки | Значение |
0x98 | Заданный сетевой том не существует |
0x9B | Неправильное значение параметра индекса каталога |
0x9C | Неправильный путь к каталогу |
Для просмотра подкаталогов в заданном каталоге вместо функции ScanDirectoryInformation() можно использовать функцию E2h прерывания INT 21h:
На входе: | AH | = | E2h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | Адрес буфера ответа. | ||
На выходе: | AL | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 2 BYTE DirectoryHandle; // индекс каталога WORD SequenceNumber; // порядковый номер BYTE PathLength; // длина поля пути BYTE SearchDirectoryPath[PathLength]; // путь поиска };
Приведем формат буфера ответа: struct REPLAY { WORD PacketLength; // размер пакета BYTE DirectoryName[16]; // имя найденного каталога BYTE CreationDate[2]; // дата создания каталога BYTE CreationTime[2]; // время создания каталога long OwnerObjectID; // идентификатор пользователя, // создавшего каталог BYTE MaximumRightsMask; // маска прав доступа BYTE Reserved; // зарезервировано WORD SubDirNumber; // номер подкаталога в // каталоге };
В процессе просмотра содержимого каталога программа должна вызывать эту функцию в цикле, задавая каждый раз (кроме первого) значение поля SequenceNumber в буфере запроса равным значению SubDirNumber, полученному в буфере ответа после предыдущего вызова функции. При первом вызове функции значение поля SequenceNumber должно быть равно нулю. Учтите, что поля SubDirNumber и SequenceNumber имеют "перевернутый" формат, т. е. младший байт поля записан по старшему адресу.
Если при вызове функции был найден подкаталог, соответствующий образцу, заданному в поле SearchDirectoryPath, регистр AL будет содержать нулевое значение.
Создание, переименование и удаление каталогов
Для работы с сетевыми каталогами вы можете использовать обычные функции MS-DOS, описанные в первом томе "Библиотеки системного программиста". Это возможно благодаря тому, что сетевая оболочка переназначает эти функции, посылая соответствующие запросы файл-серверу.
Однако операции с сетевыми каталогами лучше выполнять с помощью специально предназначенных для этого функций сетевой оболочки. В этом случае, например, при создании каталога вы сможете задавать маску прав доступа к каталогу, а также правильно распознавать ошибочные ситуации, связанные со спецификой сети.
Для создания каталога вы можете воспользоваться функцией CreateDirectory(): int CreateDirectory(BYTE DirectoryHandle, char* DirectoryPath, BYTE MaximumRightsMask);
Параметры DirectoryHandle и DirectoryPath задают создаваемый каталог. Вы можете использовать оба параметра сразу, указывая индекс каталога, в котором вы будете создавать подкаталог (параметр DirectoryHandle ), и имя создаваемого подкаталога (параметр DirectoryPath). Можно также указывать только полный путь к создаваемому каталогу, а в качестве параметра DirectoryHandle задать нулевое значение.
Параметр MaximumRightsMask задает вид доступа, разрешенный пользователям для данного каталога:
Номер бита | Доступ |
0 | Чтение файлов |
1 | Запись в файлы |
2 | Открытие файлов |
3 | Создание файлов |
4 | Удаление файлов |
5 | Можно создавать подкаталоги и задавать для создаваемых подкаталогов права доступа |
6 | Поиск файлов в каталоге |
7 | Изменение атрибутов файлов |
Например, если в маске бит 3 установлен в 1, пользователи могут создавать в каталоге файлы и подкаталоги.
Приведенная ниже программа MAKEDIR позволит вам создавать каталоги и указывать при этом маску прав доступа.
В случае успешного завершения функция CreateDirectory() возвращает нулевое значение, в противном случае - код ошибки:
Код ошибки | Значение |
0x84 | У пользователя недостаточно прав для создания подкаталога в данном каталоге |
0x98 | Указанный при создании каталога том не существует |
0xFF | Неправильно указан путь или имя каталога (например, в указанном каталоге уже существует подкаталог с таким же именем) |
Для переименования уже существующего каталога следует воспользоваться функцией RenameDirectory(): int RenameDirectory(BYTE DirectoryHandle, char* DirectoryPath, char *NewDirectoryName);
Путь к каталогу, имя которого надо изменить, задается параметрами DirectoryHandle и DirectoryPath таким же образом, как и в предыдущей функции. Параметр NewDirectoryName задает новое имя, которое должно иметь размер не более 15 байт.
Функция возвращает ноль в случае успешного завершения или код ошибки:
Код ошибки | Значение |
0x8B | У пользователя недостаточно прав для переименования подкаталога |
0x9B | Неправильно задан индекс каталога в параметре DirectoryHandle |
0x9С | Неправильно указан путь к каталогу |
0x9E | Неправильно задано имя каталога |
Для удаления каталога предназначена функция DeleteDirectory(): int DeleteDirectory(BYTE DirectoryHandle, char* DirectoryPath);
Параметры этой функции задают путь к удаляемому каталогу.
Функция возвращает нулевое значение или код ошибки:
Код ошибки | Значение |
0x8A | У пользователя недостаточно прав для удаления каталога |
0x98 | Указанный при удалении каталога том не существует |
0x9B | Неправильно задан индекс каталога в параметре DirectoryHandle |
0x9С | Неправильно указан путь к каталогу |
0x9F | Каталог используется в настоящее время кем-то из пользователей и не может быть удален |
0xA0 | Удаляемый каталог содержит файлы или подкаталоги, в то время как удалять можно только пустые каталоги |
Для создания каталога без помощи функций библиотеки NetWare C Interface вам следует воспользоваться функцией E2h прерывания INT21h:
На входе: | AH | = | E2h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 10 BYTE DirectoryHandle; // индекс каталога BYTE MaximumGightsMask; // маска прав каталога BYTE PathLength; // длина пути к каталогу BYTE DirectoryPath[PathLength]; // путь к каталогу };
В случае успешного завершения функции регистр AL содержит нулевое значение.
Для удаления каталога также можно воспользоваться функцией E2h прерывания INT 21h. Формат буфера запроса в этом случае должен быть таким: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 11 BYTE DirectoryHandle; // индекс каталога BYTE Reserved; // не используется BYTE PathLength; // длина пути к каталогу BYTE DirectoryPath[PathLength]; // путь к каталогу };
Код ошибки возвращается в регистре AL.
Для изменения имени существующего каталога с помощью функции E2h прерывания INT 21h вы должны задать буфер запроса в следующем формате: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 15 BYTE DirectoryHandle; // индекс каталога BYTE PathLength; // длина пути к каталогу BYTE DirectoryPath[PathLength]; // путь к каталогу BYTE NameLength; // длина нового имени каталога BYTE NewDirectoryName[NameLength]; // новое имя каталога };
Просмотр и изменение атрибутов
Для просмотра маски прав доступа каталога можно использовать функцию GetEffectiveDirectoryRights(): int GetEffectiveDirectoryRights(BYTE DirectoryHandle, char *DirectoryPath, BYTE *EffectiveRightsMask);
Параметры DirectoryHandle и DirectoryPath задают путь к каталогу, маску прав которого вам нужно получить. Параметр EffectiveRightsMask - указатель на байт памяти, в который будет записан байт маски прав доступа.
Функция возвращает нулевое значение или код ошибки:
Код ошибки | Значение |
0x98 | Указанный при создании каталога том не существует |
0x9B | Неправильно задан индекс каталога в параметре DirectoryHandle |
Функция SetDirectoryInformation() позволяет задать новые значения для времени и даты создания каталога, идентификатора пользователя, создавшего каталог и изменить маску прав доступа каталога: int SetDirectoryInformation(BYTE DirectoryHandle, char *DirectoryPath, BYTE *NewCreationDateAndTime, long NewOwnerObjectID, BYTE MaximumRightsMask);
Параметры DirectoryHandle и DirectoryPath задают путь к нужному нам каталогу.
Параметр NewCreationDateAndTime указывает на массив из четырех байт с новыми значениями даты и времени. Формат этого массива мы рассматривали ранее в разделе, посвященном определению содержимого сетевых каталогов (см. Рисунок 1).
Параметр NewOwnerObjectID задает идентификатор нового владельца каталога. Этот идентификатор должен быть определен в базе объектов операционной системы Novell NetWare.
Параметр MaximumRightsMask задает новое значение для маски прав доступа каталога.
Функция возвращает нулевое значение или код ошибки:
Код ошибки | Значение |
0x9B | Неправильно задан индекс каталога в параметре DirectoryHandle |
0x9C | Неправильно задан путь к каталогу |
Учтите, что для успешного выполнения функции SetDirectoryInformation() пользователь должен иметь права на изменение атрибутов каталога. Сменить же владельца каталога может только пользователь с правами супервизора.
Если вам нужно изменить только маску прав доступа каталога, удобно воспользоваться функцией ModifyMaximumRightsMask(): int ModifyMaximumRightsMask(BYTE DirectoryHandle, char *DirectoryPath, BYTE RevokeRightsMask, BYTE GrantRightsMask);
Параметры DirectoryHandle и DirectoryPath задают путь к каталогу, маску которого необходимо изменить.
Параметр RevokeRightsMask задает удаляемые права доступа, а параметр GrantRightsMask - добавляемые.
Программа, использующая эту функцию, должна иметь права на изменение атрибутов каталога, в противном случае маска прав доступа каталога изменена не будет.
Функция возвращает нулевое значение или код ошибки:
Код ошибки | Значение |
0x8С | У программы нет прав для изменения атрибутов |
0x98 | Указанный при создании каталога том не существует |
0x9C | Неправильно задан путь к каталогу |
Для того чтобы получить права доступа к каталогу вместо функции GetEffectiveDirectoryRights() можно использовать функцию E2h прерывания INT21h:
На входе: | AH | = | E2h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 3 BYTE DirectoryHandle; // индекс каталога BYTE PathLength; // длина поля пути BYTE DirectoryPath[PathLength]; // путь к каталогу };
Приведем формат буфера ответа: struct REPLAY { WORD PacketLength; // размер пакета BYTE EffectiveRightsMask; // права доступа };
Для изменения атрибутов каталога вместо функции SetDirectoryInformation() вы также можете использовать функцию E2h прерывания INT 21h, заполнив буфер запроса следующим образом: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 25 BYTE DirectoryHandle; // индекс каталога BYTE NewCreationDateAndTime[4]; // новые дата и время long NewOwnerObjectID; // идентификатор владельца BYTE MaximumRightsMask; // маска прав доступа BYTE PathLength; // длина поля пути BYTE DirectoryPath[PathLength]; // путь к каталогу };
Если вам надо изменить маску прав доступа для существующего каталога, вы можете воспользоваться той же самой функцией прерывания INT 21h. Приведем формат соответствующего буфера запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 4 BYTE DirectoryHandle; // индекс каталога BYTE RevokeRightsMask; // удаляемые права доступа BYTE GrantRightsMask; // добавляемые права доступа BYTE PathLength; // длина поля пути BYTE DirectoryPath[PathLength]; // путь к каталогу };
Во всех описанных случаях после вызова прерывания INT 21h регистр AL содержит 0 или код ошибки.
Программа DIRUNMAP
3.2.3. Программа DIRUNMAP
Программа DIRUNMAP (листинг 10) выполняет отмену отображения локального диска на сетевой каталог. Ей необходимо указать один параметр - букву, обозначающую диск, для которого необходимо отменить отображение. // ===================================================
WORD TotalBlocks, SectorsPerBlock, AvailableBlocks; WORD
Листинг 7
Программа для просмотра имен // томов текущего или первичного файл-сервера // Файл volinfo\volinfo.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#define WORD unsigned int #define BYTE unsigned char extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int GetVolumeName(int, char*);
extern "C" int GetVolumeInfoWithNumber(BYTE, char*, WORD*, WORD*, WORD*, WORD*, WORD*, WORD*);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; char VolumeName[64][16]; int ccode, i; WORD TotalBlocks, SectorsPerBlock, AvailableBlocks; WORD TotalDirectorySlots, AvailableDirectorySlots, Removable; long TotalSectors, AvailableSectors; printf("\n*VOLINFO* (C) Frolov A., 1993\n");
asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } printf("\nСмонтированные тома:\n" "----------------------------------------------\n");
printf("Имя тома\tВсего Кбайт\tСвободно Кбайт\n");
printf("----------------------------------------------");
// Цикл по томам файл-сервера. for(i=0; i<64; i++) { // Получаем и выводим имя тома ccode = GetVolumeName(i, VolumeName[i]);
printf("\n%s\t", VolumeName[i]);
// Если ошибка или тома нет, выходим из цикла if(ccode) break; if(!*(VolumeName[i])) break; // Получаем информацию о томе ccode = GetVolumeInfoWithNumber(i, VolumeName[i], &TotalBlocks, &SectorsPerBlock, &AvailableBlocks, &TotalDirectorySlots,&AvailableDirectorySlots, &Removable);
if(!ccode) { // Подсчитываем общее количество секторов на томе // и количество свободных секторов TotalSectors = (long)TotalBlocks * SectorsPerBlock; AvailableSectors = (long)AvailableBlocks * SectorsPerBlock; // Выводим размер томов и размер свободного пространства // в килобайтах. Учитываем, что размер сектора // составляет 512 байт. printf("\t%ld\t\t%ld", ((long)TotalSectors * 512L) / 1024L, ((long)AvailableSectors * 512L) / 1024L);
} } } Вот что программа VOLINFO вывела на экран, когда мы запустили ее на нашем сервере SYSPRG: *VOLINFO* (C) Frolov A., 1993 Смонтированные тома: ---------------------------------------------- Имя тома Всего Кбайт Свободно Кбайт ---------------------------------------------- SYS 140000 8084 VOL1 178864 13768 VOL2 160000 13372 VOL3 169024 924
Листинг 8
Отображение содержимого таблиц сетевой оболочки. // Файл wkstable\wkstable.cpp // // (C) A. Frolov, 1993 // ============================================================ #include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#define BYTE unsigned char // Формат таблицы номеров каналов для сетевой оболочки struct ConnectionIDTable { BYTE InUseFlag; BYTE OrderNumber; BYTE NetworkNumber[4]; BYTE NodeAddress[6]; BYTE SocketNumber[2]; BYTE ReceiveTimeout[2]; BYTE RoutingNode[6]; BYTE PacketSequenceNumber; BYTE ConnectionNumber; BYTE ConnectionStatus; BYTE MaximumTimeout[2]; BYTE Reserved[5]; }; void GetTableAddress(int Table, char far * *TableAddress);
void ShowTable(char far *DriveHandleTable);
void ShowConnIDTable(struct ConnectionIDTable far *ConnIDTable);
void ShowServerNameTable(char far *ServerNameTable);
void main(void) { // Указатели на таблицы сетевой оболочки char far *DriveHandleTable; char far *DriveFlagTable; char far *DriveServerTable; char far *ServerMappingTable; char far *ServerNameTable; struct ConnectionIDTable far *ConnIDTable; // Получаем указатели на таблицы GetTableAddress(0, &DriveHandleTable);
GetTableAddress(1, &DriveFlagTable);
GetTableAddress(2, &DriveServerTable);
GetTableAddress(3, &ServerMappingTable);
GetTableAddress(4, &ServerNameTable);
printf("\nТаблицы сетевой оболочки, (C) Frolov A., 1993\n" "---------------------------------------------\n");
// Отображаем содержимое таблицы индексов дисковых устройств printf("\nDrive Handle Table (%Fp)\n", DriveHandleTable);
printf( "------------------\n");
ShowTable(DriveHandleTable);
// Отображаем содержимое таблицы флагов дисковых устройств printf("\nDrive Flag Table (%Fp)\n", DriveFlagTable);
printf( "----------------\n");
ShowTable(DriveFlagTable);
// Отображаем содержимое таблицы отображения дисков на серверы printf("\nDrive Server Table (%Fp)\n", DriveServerTable);
printf( "------------------\n");
ShowTable(DriveServerTable);
printf("Нажмите любую клавишу для продолжения...\n");
getch();
// Отображаем содержимое таблицы каналов с серверами ConnIDTable = (struct ConnectionIDTable far *)ServerMappingTable; printf("\nConnection ID Table (%Fp)\n", ConnIDTable);
printf( "-------------------\n");
ShowConnIDTable(ConnIDTable);
// Отображаем содержимое таблицы имен серверов printf("\nServer Name Table (%Fp)\n", ServerNameTable);
printf( "-----------------\n");
ShowServerNameTable(ServerNameTable);
} // =========================================================== // Функция для отображения таблицы имен серверов // =========================================================== void ShowServerNameTable(char far *ServerNameTable) { for(int i=0; i<8; i++) { if(*(ServerNameTable + 48*i) != '\0') printf("%d: %Fs\n", i+1, ServerNameTable + 48*i);
else printf("%d: Не используется\n", i+1);
} } // =========================================================== // Функция для отображения таблицы каналов рабочей станции // =========================================================== void ShowConnIDTable(struct ConnectionIDTable far *ConnIDTable) { printf("Порядковый номер:\t");
for(int i=0; i<8; i++) { printf("%d ", i+1);
} printf("\nНомер канала:\t\t");
for(i=0; i<8; i++) { printf("%02.2X ", (ConnIDTable + i)->
ConnectionNumber);
} printf("\nСостояние канала:\t");
for(i=0; i<8; i++) { printf("%02.2X ", (ConnIDTable + i)->
ConnectionStatus);
} printf("\n");
} // =========================================================== // Функция для вывода содержимого таблиц отображения // дисковых устройств // =========================================================== void ShowTable(char far *Table) { printf("A B C D E F G H I J K L M N " "O P Q R S T U V W X Y Z\n");
for(int i=0; i<26; i++) { printf("%02.2X ",(unsigned char)*(Table +i));
} printf("\n[ \\ ] ^ _ '\n");
for(i=26; i<32; i++) { printf("%02.2X ",(unsigned char)*(Table +i));
} printf("\n");
} // =========================================================== // Функция для получения указателей на таблицы // оболочки рабочей станции // =========================================================== void GetTableAddress(int Table, char far* *TableAddress) { union REGS regs; struct SREGS sregs; regs.h.ah = 0xef; regs.h.al = Table; intdosx(®s, ®s, &sregs);
FP_OFF(*TableAddress) = regs.x.si; FP_SEG(*TableAddress) = sregs.es; } Мы запустили программу в сети, содержащей четыре файл-сервера, и вот что увидели на экране: Таблицы сетевой оболочки, (C) Frolov A., 1993 --------------------------------------------- Drive Handle Table (C143:01A0) ------------------ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 02 02 02 00 06 03 04 05 [ \ ] ^ _ ' 00 00 00 00 00 00 Drive Flag Table (C143:01C0) ---------------- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 80 80 80 80 80 80 80 80 80 01 00 00 00 00 00 00 00 00 01 01 01 00 01 01 01 01 [ \ ] ^ _ ' 00 00 00 00 00 00 Drive Server Table (C143:01E0) ------------------ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 04 03 02 00 01 01 01 01 [ \ ] ^ _ ' 00 00 00 00 00 00 Нажмите любую клавишу для продолжения... Connection ID Table (C143:0254) ------------------- Порядковый номер: 1 2 3 4 5 6 7 8 Номер канала: 04 0B 01 01 FF FF FF FF Состояние канала: FF FF FF FF 00 00 00 00 Server Name Table (C143:0354) ----------------- 1: SYSPRG 2: SMARTNET 3: NETLAB 4: WINLAB 5: Не используется 6: Не используется 7: Не используется 8: Не используется
Из таблицы Server Name Table видно, что рабочая станция создала каналы с серверами SYSPRG, SMARTNET, NETLAB и WINLAB.
Анализируя содержимое таблицы Drive Flag Table, можно сделать вывод, что рабочая станция имеет локальные диски от A: до I:, диски J:, S:, T:, U:, W:, X:, Y:, Z: постоянно отображены на сетевые каталоги, т. е. это сетевые диски.
Из таблицы Drive Server Table видно, что диски J:, W:, X:, Y:, Z: отображены на каталоги сервера SYSPRG (номер канала соответствует позиции имени файл-сервера в таблице имен). Диск U: отображен на сервер SMARTNET (канал 2), диск T: - на сервер NETLAB (канал 3), диск S: - на сервер WINLAB (канал 4).
Таблица Drive Handle Table позволяет сетевой оболочке определить, на какие конкретно каталоги отображены соответствующие диски, так как она содержит индекс таблицы отображения соответствующего файл-сервера.
Из таблицы Connection ID Table можно получить информацию о том, какие из 8 имеющихся каналов задействованы и какие номера каналов используют серверы для работы с нашей станцией. Например, сервер SYSPRG использует канал с номером 04h, сервер SMARTNET - канал с номером 0Bh, а серверы NETLAB и WINLAB - каналы с номером 01h. Пусть вас не смущает, что последние два сервера используют один и тот же номер канала: эти номера соответствуют таблице, расположенной в сервере, а не в рабочей станции.
Листинг 9
Отображение локальных дисков на // сетевые каталоги // Файл dirmap\dirmap.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int AllocPermanentDirectoryHandle(BYTE, char *, char, BYTE*, BYTE*);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; BYTE NewDirectoryHandle; BYTE RightsMask; int ccode; printf("\n*DIRMAP* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать букву, обозначающую имя // локального диска и путь к сетевому каталогу if(argc < 3) { printf("Укажите локальный диск и путь " "к каталогу, \nнапример: dirmap f sys:users\n");
return; } // Параметры должны быть заданы заглавными буквами strupr(argv[1]);
strupr(argv[2]);
// Создаем новый индекс каталога и отображаем диск ccode = AllocPermanentDirectoryHandle(0, argv[2], argv[1][0], &NewDirectoryHandle, &RightsMask);
// Если ошибок не было, выводим индекс каталога // и маску прав для каталога if(!ccode) printf("Индекс каталога: %d, маска прав: %02.2X\n", NewDirectoryHandle, RightsMask);
else printf("Ошибка %02.2X\n", ccode);
}
Листинг 10
Отмена отображения локального диска на // сетевой каталог // Файл dirunmap\dirunmap.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" BYTE GetDirectoryHandle(BYTE);
extern "C" int DeallocateDirectoryHandle(BYTE);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; BYTE DirectoryHandle; int ccode; printf("\n*DIRUNMAP* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать букву, обозначающую имя // локального диска и путь к сетевому каталогу if(argc < 2) { printf( "Укажите локальный диск, например: dirunmap f\n");
return; } // Параметр должен быть задан заглавной буквой strupr(argv[1]);
// Получаем индекс каталога, на который отображен указанный диск DirectoryHandle = GetDirectoryHandle(argv[1][0] - 'A');
// Если диск не отображен на сетевой каталог, // выводим сообщение об ошибке if(DirectoryHandle) printf("Индекс каталога: %d\n", DirectoryHandle);
else { printf("Диск не отображен на сетевой каталог\n");
return; } // Отменяем отображение диска ccode = DeallocateDirectoryHandle(DirectoryHandle);
// Если ошибок не было, выводим индекс каталога // и маску прав для каталога if(ccode) printf("Ошибка %02.2X\n", ccode);
else printf("Диск %c удален\n", argv[1][0]);
}
Листинг 11
Просмотр списка подкаталогов // сетевого каталога // Файл dirscan\dirscan.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int ScanDirectoryInformation(BYTE, char *, int *, char *, BYTE *, long *, BYTE *);
extern "C" int GetBinderyObjectName(long, char *, WORD *);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int SequenceNumber; char DirectoryName[16]; BYTE CreationDataAndTime[4]; long OwnerObjectID; BYTE RightsMask; int ccode; char ObjectName[48]; WORD ObjectType; printf("\n*DIRSCAN* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // В качестве аргумента необходимо задать // путь к просматриваемому каталогу в виде SYS:USERS\* if(argc < 2) { printf("Укажите путь к каталогу, " "например: dirscan sys:users\\*\n");
return; } printf("Содержимое каталога %s\n", argv[1]);
printf("--------------------------------------------\n");
printf("Имя \tКто владелец каталога\n");
printf("--------------------------------------------\n");
// Путь должен быть задан заглавными буквами strupr(argv[1]);
// Цикл просмотра каталога for(SequenceNumber = 0;;) { // Получаем информацию о содержимом каталога ccode = ScanDirectoryInformation(0, argv[1], &SequenceNumber, DirectoryName, CreationDataAndTime, &OwnerObjectID, &RightsMask);
// Если были ошибки или каталог пуст, завершаем цикл if(ccode) break; if(DirectoryName[0] == '\0') break; // Выводим имя каталога printf("%-12s", DirectoryName);
// Если для каталога определен владелец, // получаем и выводим имя владельца if(OwnerObjectID) { GetBinderyObjectName(OwnerObjectID, ObjectName, &ObjectType);
printf("\t%-12s \n", ObjectName);
} else printf("\t <Нет сведений о владельце>
\n");
} }
Листинг 12
Создание каталога // Файл makedir\makedir.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int CreateDirectory(BYTE, char*, BYTE);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; BYTE RightsMask; int ccode; printf("\n*MAKEDIR* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать букву, обозначающую имя // локального диска и путь к сетевому каталогу if(argc < 3) { printf("Укажите имя создаваемого каталога и " "права доступа, \nнапример: makedir sys:users RW");
return; } // Параметры должны быть заданы заглавными буквами strupr(argv[1]);
strupr(argv[2]);
// Определяем маску прав, разбирая второй // параметр, заданный при запуске программы RightsMask = 0x00; for(int i = 0; argv[2][i] != '\0' ; i++) { switch(argv[2][i]) { case 'R': RightsMask |= 1; break; case 'W': RightsMask |= 2; break; case 'O': RightsMask |= 4; break; case 'C': RightsMask |= 8; break; case 'D': RightsMask |= 0x10; break; case 'P': RightsMask |= 0x20; break; case 'S': RightsMask |= 0x40; break; case 'M': RightsMask |= 0x80; break; case '-': break; default: printf("Ошибка в параметрах\n");
return; } } // Создаем каталог ccode = CreateDirectory(0, argv[1], RightsMask);
if(!ccode) printf("Каталог создан\n");
else printf("Ошибка %02.2X\n", ccode);
} Программа RENMDIR (листинг 13) позволяет переименовать существующий сетевой каталог. Путь к каталогу и его новое имя следует задать в качестве параметров при запуске программы. // ===================================================
Листинг 13
Переименование каталога // Файл renmdir\renmdir.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int RenameDirectory(BYTE, char *, char *);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int ccode; printf("\n*RENMDIR* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать букву, обозначающую имя // локального диска и путь к сетевому каталогу if(argc < 3) { printf("Укажите путь к каталогу и " "новое имя, \nнапример: renmdir sys:users usr");
return; } // Параметры должны быть заданы заглавными буквами strupr(argv[1]);
strupr(argv[2]);
// Переименовываем каталог ccode = RenameDirectory(0, argv[1], argv[2]);
if(!ccode) printf("Каталог переименован\n");
else printf("Ошибка %02.2X\n", ccode);
} Программа DELDIR (листинг 14) удаляет каталог, путь к которому задан в качестве параметра при запуске программы. // ===================================================
Листинг 14
Удаление каталога // Файл deldir\deldir.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int DeleteDirectory(BYTE, char *);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int ccode; printf("\n*DELDIR* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать букву, обозначающую имя // локального диска и путь к сетевому каталогу if(argc < 2) { printf("Укажите путь к каталогу, " "\nнапример: deldir sys:users");
return; } // Параметр должен быть задан заглавными буквами strupr(argv[1]);
// Удаляем каталог ccode = DeleteDirectory(0, argv[1]);
if(!ccode) printf("Каталог удален\n");
else printf("Ошибка %02.2X\n", ccode);
}
Листинг 15
Просмотр маски прав доступа к каталогу // Файл getmask\getmask.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int GetEffectiveDirectoryRights(BYTE, char*, BYTE*);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; BYTE RightsMask; int ccode; printf("\n*GETMASK* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать букву, обозначающую имя // локального диска и путь к сетевому каталогу if(argc < 2) { printf("Укажите путь к каталогу, " "\nнапример: getmask sys:users");
return; } // Параметр должен быть задан заглавными буквами strupr(argv[1]);
// Удаляем каталог ccode = GetEffectiveDirectoryRights(0, argv[1], &RightsMask);
if(!ccode) { printf("Права доступа: %02.2X\n", RightsMask);
if(RightsMask & 0x01) printf("Read\t(Чтение)\n");
if(RightsMask & 0x02) printf("Write\t(Запись)\n");
if(RightsMask & 0x04) printf( "Open\t(Открытие файлов)\n");
if(RightsMask & 0x08) printf("Create\t(Создание)\n");
if(RightsMask & 0x10) printf("Delete\t(Уничтожение)\n");
if(RightsMask & 0x20) printf("Parential\t" "(Определение прав)\n");
if(RightsMask & 0x40) printf("Search\t(Поиск)\n");
if(RightsMask & 0x80) printf("Modify\t" "(Изменение атрибутов)\n");
} else printf("Ошибка %02.2X\n", ccode);
}
Листинг 16
Изменение маски прав доступа // Файл setmask\setmask.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int ModifyMaximumRightsMask(BYTE, char*, BYTE,BYTE);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; BYTE RevokeRightsMask; BYTE GrantRightsMask; int ccode; printf("\n*SETMASK* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать путь к каталогу, удаляемые // и добавляемые права доступа if(argc < 4) { printf("Укажите путь к каталогу, " "удаляемые и добавляемые права доступа, " "\nнапример: setmask sys:users W RO");
return; } // Параметры должны быть заданы заглавными буквами strupr(argv[1]);
strupr(argv[2]);
strupr(argv[3]);
// Определяем маску удаляемых прав, разбирая второй // параметр, заданный при запуске программы RevokeRightsMask = 0x00; for(int i = 0; argv[2][i] != '\0' ; i++) { switch(argv[2][i]) { case 'R': RevokeRightsMask |= 1; break; case 'W': RevokeRightsMask |= 2; break; case 'O': RevokeRightsMask |= 4; break; case 'C': RevokeRightsMask |= 8; break; case 'D': RevokeRightsMask |= 0x10; break; case 'P': RevokeRightsMask |= 0x20; break; case 'S': RevokeRightsMask |= 0x40; break; case 'M': RevokeRightsMask |= 0x80; break; case '-': break; default: printf("Ошибка в параметрах\n");
return; } } // Определяем маску добавляемых прав, разбирая // третий параметр, заданный при запуске программы GrantRightsMask = 0x00; for(i = 0; argv[3][i] != '\0' ; i++) { switch(argv[3][i]) { case 'R': GrantRightsMask |= 1; break; case 'W': GrantRightsMask |= 2; break; case 'O': GrantRightsMask |= 4; break; case 'C': GrantRightsMask |= 8; break; case 'D': GrantRightsMask |= 0x10; break; case 'P': GrantRightsMask |= 0x20; break; case 'S': GrantRightsMask |= 0x40; break; case 'M': GrantRightsMask |= 0x80; break; case '-': break; default: printf("Ошибка в параметрах\n");
return; } } // Изменяем маску доступа каталога ccode = ModifyMaximumRightsMask(0, argv[1], RevokeRightsMask, GrantRightsMask);
if(!ccode) printf("Маска прав изменена\n");
else printf("Ошибка %02.2X\n", ccode);
}
Атрибуты файлов
4.1. Атрибуты файлов
4.2. Поиск файлов
4.3. Изменение атрибутов
4.4. Копирование файлов
4.5. Удаление файлов
В этом разделе мы расскажем вам об использовании некоторых, самых интересных, на наш взгляд, функций сетевой оболочки Novell NetWare, предназначенных для работы с файлами.
Практически все обычные операции с файлами, такие, как запись, чтение, перезапись по месту, удаление и т. п., можно выполнять с использованием обычных функций MS-DOS, подробно рассмотренных нами ранее в первом томе "Библиотеки системного программиста". Однако для выполнения некоторых операций с файлами, расположенными на файл-сервере, вам не обойтись без специальных функций сетевой оболочки. К таким операциям можно отнести, например, просмотр и изменение байта атрибутов и байта расширенных атрибутов файла, копирование файлов с диска файл-сервера на диск файл-сервера без вовлечения в эту операцию рабочей станции и т. д.
Поиск файлов
Программа FSCAN
Листинг 17
Изменение атрибутов
Программа SETEATTR
Удаление файлов
Программа FERASE
Программа FCOPY
4.4.1. Программа FCOPY
Программа FCOPY (листинг 19) демонстрирует использование функции FileServerFileCopy(). При запуске этой программе необходимо в качестве параметров указать пути к входному и выходному файлам. // =============================================================
Программа FERASE
4.5.1. Программа FERASE
Программа FERASE (листинг 20) демонстрирует использование функции EraseFiles(). При запуске программе в качестве параметра необходимо передать путь к удаляемому файлу. // ===================================================
Программа FSCAN
4.2.1. Программа FSCAN
Приведем программу FSCAN (листинг 17), которая выводит список файлов, расположенных в каталоге, путь к которому задается в качестве параметра при запуске программы. Для каждого найденного в каталоге файла программа выводит имя файла, его размер, байт атрибутов и байт расширенных атрибутов, а также имя пользователя, создавшего файл. // ===================================================
Программа SETEATTR
4.3.1. Программа SETEATTR
В программе SETEATTR (листинг 18) мы продемонстрируем использование функции SetExtendedFileAttributes() для изменения байта расширенного атрибута файла, путь к которому указывается в качестве параметра при запуске программы. // ===================================================
Атрибуты файлов
По сравнению с файлами MS-DOS файлы, расположенные на файл-сервере Novell NetWare, имеют больше атрибутов. В этом разделе мы рассмотрим эти атрибуты.
В отличие от MS-DOS файловая система Novell NetWare для хранения атрибутов файлов использует не один, а два байта. Первый байт называется байтом атрибутов (File Attributes Byte), второй - байтом расширенных атрибутов (Extended File Attributes Byte).
Приведем назначение отдельных битов байта атрибутов:
Бит | Назначение |
0 | Read Only: файл можно читать, но нельзя в него писать. Этот файл нельзя также удалять или переименовывать |
1 | Hidden: скрытый файл, не появляется в списке файлов при поиске в каталоге обычными средствами |
2 | System: системный файл, не появляется в списке файлов при поиске в каталоге обычными средствами |
3 | Execute Only: файл может быть загружен только для выполнения. Этот файл нельзя читать или перезаписывать. В операционной системе Novell NetWare не существует средств для сброса бита Execute Only; поэтому, если вы установите этот бит, вы навсегда потеряете доступ к файлу на чтение и запись |
4 | Subdirectory: данный элемент оглавления каталога описывает не файл, а подкаталог |
5 | Archive: этот бит установлен, если после выполнения операции выгрузки файла сам файл был изменен |
6 | Зарезервировано |
7 | Shareable: к данному файлу разрешен одновременный доступ со стороны нескольких пользователей, расположенных на разных рабочих станциях |
Обратим ваше внимание на бит Execute Only. Если вы установите этот бит, вызвав соответствующую функцию или воспользовавшись утилитами Novell NetWare, никто (даже супервизор) не будет иметь доступа на чтение к этому файлу. Такая возможность полезна для защиты программ от несанкционированного копирования и дезассемблирования, однако этот способ обладает рядом недостатков.
Во-первых, вы не можете установить бит Execute Only у оверлеев, так как перед запуском их надо загрузить в память, а такая операция запрещена для "только выполняемых" файлов. Файлы, содержащие программы для Microsoft Windows, также нельзя отмечать как Execute Only из-за того, что они содержат ресурсы, подгружаемые после запуска программы.
Во-вторых, никто не помешает злоумышленнику поймать вашу программу в памяти уже после ее запуска. Для этого он может воспользоваться, например, резидентными отладчиками, такими, как AFD или Turbo Debugger. Так что не переоценивайте защиту при помощи атрибута Execute Only.
Если вы случайно установили бит Execute Only для файла, содержащего данные, можете смело удалять этот файл - вам никогда не удастся сбросить бит Execute Only и прочитать содержимое файла.
Байт расширенных атрибутов имеет следующий формат:
Бит | Назначение |
0, 1, 2 | Search Mode: биты 0, 1 и 2 задают режим поиска программного файла при запуске программы на выполнение. Мы не будем рассматривать эти биты для сокращения объема книги, подробности о режимах поиска и дисках поиска вы можете узнать из руководства по библиотеке NetWare C Interface |
3 | Зарезервировано |
4 | Transaction Bit: при работе с файлом используется обработка транзакций. Файл, у которого установлен этот бит, не может быть удален или переименован |
5 | Index: для файла размером больше 2 Мбайт для более быстрого доступа организуется индекс по таблице FAT. Этот бит не используется в Novell NetWare версии 3.11 |
6 | Зарезервировано |
7 | Зарезервировано |
Поиск файлов
Для поиска файлов в каталоге вы можете воспользоваться функцией _ScanFileInformation() (в документации по библиотеке Netware C Interface эта функция называется ScanFileInformation(), однако в самой библиотеке нет функции с этим названием, зато есть функция _ScanFileInformation(), которая делает то же самое). Приведем прототип функции: int _ScanFileInformation(BYTE DirectoryHandle, char *FilePath, BYTE SearchAttributes, int *SequenceNumber, char *FileName, BYTE *FileAttributes, BYTE *ExtendedFileAttributes, long *FileSize, char *CreationDate, char *LastAccessDate, char *LastUpdateDateAndTime, char *LastArchiveDateAndTime, long *FileOwnerID);
Параметр DirectoryHandle при вызове функции может содержать индекс просматриваемого каталога или ноль. В последнем случае путь к просматриваемому каталогу должен быть задан через параметр FilePath в виде текстовой строки, закрытой двоичным нулем.
Параметр SearchAttributes определяет, какие типы файлов нужно найти. Этот параметр может принимать следующие значения:
Параметр | Значение |
0 | Обычные файлы |
2 | Обычные и скрытые файлы |
4 | Обычные и системные файлы |
6 | Обычные, скрытые и системные файлы |
Параметр SequenceNumber при первом вызове функции должен указывать на переменную, которая имеет значение 0xFFFF. Когда программа будет просматривать содержимое каталога, вызывая функцию в цикле, содержимое этой переменной будет изменяться автоматически.
Параметр FileName должен указывать на буфер размером 15 байт, в который будет записано имя найденного файла.
Атрибуты и расширенные атрибуты будут записаны в байты памяти, которые необходимо указать при помощи параметров FileAttributes и ExtendedFileAttributes.
Размер найденного файла будет записан в переменную, заданную при помощи параметра FileSize.
Сведения о дате создания файла и дате последнего доступа к файлу будут записаны в буферы размером 2 байта, заданные соответственно параметрами CreationDate и LastAccessDate.
Дата и время последнего обновления содержимого файла будут записаны в буфер размером 4 байта, заданный параметром LastUpdateDateAndTime, а дата и время выгрузки - в аналогичный буфер, заданный параметром LastArchiveDateAndTime.
Идентификатор пользователя, создавшего файл, будет записан в переменную типа long, адрес которой задается параметром FileOwnerID. По этому идентификатору с помощью функции GetBinderyObjectName() можно получить имя пользователя.
Функция поиска файлов _ScanFileInformation() возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x89 | У программы нет прав для поиска файлов |
0xFF | Файл не найден |
Для поиска файлов в каталоге можно использовать функцию E3h
прерывания INT21h:
На входе: | AH | = | E3h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа; | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 15 WORD SequenceNumber; // номер для просмотра BYTE DirectoryHandle; // индекс каталога BYTE SearchAttributes; // тип файлов для поиска BYTE PathLength; // длина поля пути BYTE DirectoryPath[PathLength]; // путь к каталогу };
Буфер ответа имеет следующий формат: struct REPLAY { WORD PacketLength; // размер пакета WORD SequenceNumber; // номер для просмотра BYTE FileName[15]; // имя файла BYTE FileAttributes; // атрибуты файла BYTE ExtendedFileAttributes; // расширенные // атрибуты файла long FileSize; // размер файла в байтах BYTE CreationDate[2]; // дата создания BYTE LastAccessDate[2]; // дата последнего доступа BYTE LastUpdateDateAndTime[4]; // дата и время // обновления BYTE LastArchiveDateAndTime[4]; // дата и время // выгрузки BYTE Reserved[60]; // зарезервировано };
При вызове этой функции в цикле в первый раз в поле SequenceNumber буфера запроса необходимо записать значение 0xFFFF. При последующих вызовах необходимо уменьшать на единицу значение, полученное в поле SequenceNumber буфера ответа и записывать его в поле SequenceNumber буфера запроса. Следует учитывать, что байты в поле SequenceNumber записаны в обратном порядке, поэтому перед уменьшением необходимо переставить байты. Это можно сделать при помощи функции IntSwap() из библиотеки NetWare C Interface.
Изменение атрибутов
Для изменения атрибутов файлов, а также другой информации, такой, как время создания файла и идентификатор владельца, можно воспользоваться функцией SetFileInformation(): int SetFileInformation(BYTE DirectoryHandle, char *FilePath, BYTE SearchAttributes, BYTE FileAttributes, BYTE ExtendedFileAttributes, char *CreationDate, char *LastAccessDate, char *LastUpdateDateAndTime, char *LastArchiveDateAndTime, long *FileOwnerID);
Параметры этой функции имеют такое же назначение, что и параметры функции _ScanFileInformation(): с помощью параметров DirectoryHandle и FilePath вы должны указать путь к файлу, остальные параметры задают новые значения для атрибутов файла и другой информации, имеющей отношение к файлу.
Функция SetFileInformation() возвращает 0 при успешном завершении или код ошибки.
Для изменения атрибутов файлов и другой информации о файлах можно использовать функцию E3h прерывания INT21h:
На входе: | AH | = | E3h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 16 BYTE FileAttributes; // атрибуты файла BYTE ExtendedFileAttributes; // расширенные // атрибуты файла long Reserved; // зарезервировано BYTE CreationDate[2]; // дата создания BYTE LastAccessDate[2]; // дата последнего доступа BYTE LastUpdateDateAndTime[4]; // дата и время // обновления BYTE LastArchiveDateAndTime[4]; // дата и время выгрузки BYTE Reserved[60]; // зарезервировано BYTE DirectoryHandle; // индекс каталога BYTE SearchAttributes; // тип файлов для поиска BYTE PathLength; // длина поля пути BYTE DirectoryPath[PathLength]; // путь к каталогу };
Для изменения байта расширенных атрибутов удобно использовать функцию SetExtendedFileAttributes(): int SetExtendedFileAttributes(char *FilePath, BYTE *NewExtendedFileattributes);
Параметр FilePath задает путь к файлу, а параметр NewExtendedFileattributes - новое значение для байта расширенных атрибутов.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | У программы нет прав для изменения атрибута |
0xFF | Файл не найден |
Для того чтобы узнать байт расширенного атрибута файла, используйте функцию GetExtendedFileAttributes(): int GetExtendedFileAttributes(char *FilePath, BYTE *ExtendedFileattributes);
Параметр FilePath задает путь к файлу, параметр ExtendedFileattributes определяет адрес байта памяти, в который будет записано значение байта расширенных атрибутов файла.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | У программы нет прав для просмотра каталога |
0xFF | Файл не найден |
Для получения и изменения расширенных атрибутов файла и можно использовать функцию B6h прерывания INT 21h:
На входе: | AH | = | B6h; |
AL | = | Выполняемая функция: 00h - получить байт расширенного атрибута; 01h - изменить байт расширенного атрибута; | |
DS:DX | = | Адрес буфера, содержащего путь к файлу; | |
CL | = | Новое значение расширенного атрибута. | |
На выходе: | AL | = | 0, если операция завершилась без ошибок, или код ошибки: FCh - нет прав для выполнения функции; FFh - файл не найден; |
CL | = | Если при вызове функции регистр AL содержал значение 00h, в регистр CL будет записано значение расширенного атрибута указанного файла. |
Копирование файлов
Ваша программа может копировать файлы либо с локального диска на локальный, либо с локального на сетевой, либо с сетевого на сетевой. Если файл копируется из одного сетевого каталога в другой сетевой каталог, причем эти каталоги расположены на одном файл-сервере, для копирования имеет смысл использовать специальную функцию FileServerFileCopy(). В этом случае процесс копирования будет выполняться непосредственно на файл-сервере, без передачи файла по сети.
Приведем прототип функции FileServerFileCopy(): int FileServerFileCopy(int FromHandle, int ToHandle, long SourceFileOffset, long DestinationFileOffset, long NumberOfBytesToCopy, long *BytesCopied);
Параметры FromHandle и ToHandle указывают индексы файлов, участвующих в процессе копирования. Данные копируются из файла, задаваемого параметром FromHandle в файл, задаваемый параметром ToHandle.
Индексы файлов должны быть получены при помощи вызова функции MS-DOS с кодом 3Dh (открыть файл). Если файла, в который будут копироваться данные, нет на диске, его необходимо создать и открыть при помощи функции MS-DOS с кодом 3Ch. В программе, составленной на языке Си, вы можете использовать для открытия и создания файлов функцию open(), входящую в стандартную библиотеку транслятора.
Параметры SourceFileOffset и DestinationFileOffset задают смещение в исходном и выходном файлах. Если вы копируете файл в новый, эти параметры должны иметь нулевое значение.
Параметр NumberOfBytesToCopy задает количество копируемых байт. Если вам надо скопировать весь файл, его длину можно узнать по индексу файла при помощи функции filelength() из стандартной библиотеки транслятора.
Параметр BytesCopied - указатель на переменную, в которую будет записано количество действительно скопированных байт.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x83 | Ошибка ввода/вывода на сетевом диске |
0x96 | Мало оперативной памяти на файл-сервере |
Для копирования файлов можно использовать функцию F3h прерывания INT21h:
На входе: | AH | = | F3h; |
ES:DI | = | Адрес буфера запроса. | |
На выходе: | AL | Код ошибки или 0, если операция завершилась без ошибок; | |
DX,CX | = | Количество скопированный байт. Старший байт находится в регистре DX, младший - в регистре CX. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD FromHandle; // индекс входного файла WORD ToHandle; // индекс выходного файла long SourceFileOffset; // смещение входного файла long DestinationFileOffset; // смещение выходного файла long NumberOfBytesToCopy; // сколько байт копировать };
Удаление файлов
Для удаления файлов из сетевых каталогов можно использовать функцию EraseFiles(): int EraseFiles(BYTE DirectoryHandle, char *FilePath, BYTE SearchAttributes);
Эта функция удаляет файл, заданный параметрами DirectoryHandle и FilePath, если его атрибуты соответствуют указанным при помощи параметра SearchAttributes. Последнее означает, что, если вы собираетесь стирать нормальные, скрытые и системные файлы, для параметра SearchAttributes необходимо задать значение 06h.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x98 | Указанный сетевой том не существует |
0x9B | Индекс файла указан неправильно |
0x9C | Путь к файлу указан неправильно |
0xFF | Файл не найден |
BYTE FileAttributes; BYTE ExtendedFileAttributes; long
Листинг 17
Просмотр списка файлов в каталоге // Файл fscan\fscan.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int _ScanFileInformation(BYTE, char *, BYTE, int *, char *, BYTE *, BYTE *, long *, char *, char *, char *, char *, long *);
extern "C" int GetBinderyObjectName(long, char *, WORD *);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int SequenceNumber; char FileName[16]; BYTE FileAttributes; BYTE ExtendedFileAttributes; long FileSize; BYTE CreationDate[2]; BYTE LastAccessDate[2]; BYTE LastUpdateDateAndTime[4]; BYTE LastArchiveDateAndTime[4]; long FileOwnerID; int ccode; char ObjectName[48]; WORD ObjectType; printf("\n*FSCAN* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // В качестве аргумента необходимо задать // путь к просматриваемому каталогу в виде SYS:USERS\* if(argc < 2) { printf("Укажите путь к каталогу, " "например: dirscan sys:users\\*\n");
return; } printf("Список файлов в каталоге %s\n", argv[1]);
printf("--------------------------------------------" "-------------\n");
printf("Имя \tРазмер\tАтрибуты\tВладелец\n");
printf("--------------------------------------------" "-------------\n");
// Путь должен быть задан заглавными буквами strupr(argv[1]);
// Цикл просмотра каталога for(SequenceNumber = 0xFFFF;;) { // Получаем информацию о содержимом каталога ccode = _ScanFileInformation(0, argv[1], 6, &SequenceNumber, FileName, &FileAttributes, &ExtendedFileAttributes, &FileSize, CreationDate, LastAccessDate, LastUpdateDateAndTime, LastArchiveDateAndTime, &FileOwnerID);
// Если были ошибки, завершаем цикл if(ccode) break; if(FileName[0] == '\0') break; // Выводим имя файла printf("%-12s", FileName);
// Выводим размер файла printf("\t%ld", FileSize);
// Выводим байт атрибутов и байт расширенных атрибутов printf("\t%02.2X %02.2X\t", FileAttributes, ExtendedFileAttributes);
// Если для каталога определен владелец, // получаем и выводим имя владельца if(FileOwnerID) { GetBinderyObjectName(FileOwnerID, ObjectName, &ObjectType);
printf("\t%-12s \n", ObjectName);
} else printf("\t <Нет сведений о владельце>
\n");
} } Приведем фрагменты выдачи программы FSCAN при просмотре файлов из каталога SYS:SYSTEM файл-сервера Novell NetWare версии 3.11: *FSCAN* (C) Frolov A., 1993 Список файлов в каталоге sys:system\* --------------------------------------------------------- Имя Размер Атрибуты Владелец --------------------------------------------------------- SYS$LOG.ERR 10852 20 00 SYSPRG README.BTR 6689 00 00 SUPERVISOR *************** CLIB.NLM 232842 81 00 SYSPRG 3C503.LAN 11856 81 00 SYSPRG PS2MFM.DSK 8759 81 00 SYSPRG INSTALL.NLM 160613 81 00 SYSPRG NE2000.LAN 11636 81 00 SYSPRG *************** BCONNLM.HLP 1583 81 00 SYSPRG BROUTER.NLM 15884 81 00 SYSPRG BTRIEVE.NLM 64616 01 00 SYSPRG NET$OBJ.SYS 2560 26 10 FROLOV BTRIEVE.TRN 4096 00 00 SYSPRG PRODUCTS.DAT 5120 00 00 SYSPRG AUTOEXEC.NCF 203 00 00 SYSPRG MODEM.CFG 46 00 00 FROLOV IBM$EMS.HLP 84047 00 00 FROLOV IBM$DRV.OVL 2144 00 00 FROLOV IBM$EMS.OVL 405 00 00 FROLOV VIR.DAT 29934 20 00 FROLOV NET$PROP.SYS 8364 26 10 FROLOV NET$VAL.SYS 33654 26 10 FROLOV NETSHLD.NLM 246146 20 00 FROLOV VIR$CFG.DAT 838 20 00 SYSPRG VIR$LOG.DAT 3146 20 00 SYSPRG
Файлы с атрибутами 00h и 20h - обычные файлы. Файлы с атрибутами 26h - скрытые и системные. Файл BTRIEVE.NLM имеет байт атрибутов 01, этот файл можно только читать. Файлы с атрибутом 81 - только читаемые файлы, к которым возможен одновременный доступ со стороны нескольких пользователей.
В приведенном выше списке есть файлы, у которых установлен бит Transaction Bit. Для этих файлов включен механизм обработки транзакций, гарантирующий сохранность содержимого файла при аварии в электропитающей сети или по другим аналогичным причинам.
Листинг 18
Изменение байта расширенных атрибутов // Файл seteattr\seteattr.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int SetExtendedFileAttributes(char*, BYTE);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; BYTE NewExtendedFileattributes; int ccode; printf("\n*SETEATTR* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать путь к каталогу, удаляемые // и добавляемые права доступа if(argc < 3) { printf("Укажите путь к каталогу и " "устанавливаемый бит (T или I), " "\nнапример: seteattr sys:users\my.dat T");
return; } // Параметры должны быть заданы заглавными буквами strupr(argv[1]);
strupr(argv[2]);
// Определяем значение байта расширенных атрибутов NewExtendedFileattributes = 0x00; for(int i = 0; argv[2][i] != '\0' ; i++) { switch(argv[2][i]) { case 'T': NewExtendedFileattributes |= 0x10; break; case 'I': NewExtendedFileattributes |= 0x20; break; case '-': NewExtendedFileattributes = 0; break; default: printf("Ошибка в параметрах\n");
return; } } // Изменяем байт расширенных атрибутов ccode = SetExtendedFileAttributes(argv[1], NewExtendedFileattributes);
if(!ccode) printf("Байт расширенных атрибутов изменен\n");
else printf("Ошибка %02.2X\n", ccode);
}
Листинг 19
Копирование файлов, расположенных на файл-сервере // Файл fcopy\fcopy.cpp // // (C) A. Frolov, 1993 // ============================================================= #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
#include <sys\stat.h>
extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int FileServerFileCopy(int, int, long, long, long, long*);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int FromHandle; int ToHandle; long BytesCopied; int ccode; printf("\n*FCOPY* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать путь к каталогу, удаляемые // и добавляемые права доступа if(argc < 3) { printf("Укажите пути к копируемым файлам, " "\nнапример: fcopy f:\users\my.dat f:\us\my.dat");
return; } // Параметры должны быть заданы заглавными буквами strupr(argv[1]);
strupr(argv[2]);
// Открываем входной файл в двоичном режиме для чтения if ((FromHandle = open(argv[1], O_RDONLY | O_BINARY)) == -1) { printf("Не могу открыть исходный файл\n");
return; } // Открываем выходной файл в двоичном режиме для чтения // и записи, разрешаем создание файла, если его еще нет, // разрешаем запись в файл if ((ToHandle = open(argv[2], O_CREAT | O_RDWR | O_BINARY, S_IWRITE)) == -1) { printf("Не могу открыть выходной файл\n");
return; } // Копируем файл, длину файла определяем при помощи // функции filelength() ccode = FileServerFileCopy(FromHandle, ToHandle, 0, 0, filelength(FromHandle), &BytesCopied);
if(!ccode) printf("Файл скопирован\n");
else printf("Ошибка %02.2X\n", ccode);
// Закрываем файлы close(FromHandle);
close(ToHandle);
}
Листинг 20
Удаление файлов // Файл ferase\ferase.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int EraseFiles(BYTE, char*, BYTE);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int ccode; printf("\n*FERASE* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Необходимо задать путь к каталогу, удаляемые // и добавляемые права доступа if(argc < 2) { printf("Укажите путь к удаляемому файлу," "\nнапример: ferase sys:users\my.dat");
return; } // Параметр должен быть задан заглавными буквами strupr(argv[1]);
ccode = EraseFiles(0, argv[1], 6);
if(!ccode) printf("Файл %s удален\n", argv[1]);
else printf("Ошибка %02.2X\n", ccode);
}
Блокирование файлов
5.1. Блокирование файлов
Блокирование физических записей
Блокирование логических записей
Семафоры
Блокирование файлов
Программа FLOCK
Листинг 21
Блокирование физических записей
Программа PHYSLOCK
Программа RECACC
Блокирование логических записей
Программа LOGLOCK
Семафоры
Программа SEMSIGN
Программа FLOCK
5.1.1. Программа FLOCK
Приведем пример программы FLOCK (листинг 21), выполняющей блокирование файлов средствами сетевой оболочки.
Вначале программа в цикле запрашивает пути к блокируемым файлам до тех пор, пока оператор вместо имени файла не введет символ "-". Каждый введенный файл добавляется в группу при помощи функции LogFile().
Затем вся группа блокируется функцией LockFileSet().
Далее программа ожидает нажатия на любую клавишу. Попробуйте просмотреть содержимое заблокированных файлов с другой рабочей станции - это у вас не получится.
После того как вы нажмете на любую клавишу, программа разблокирует группу файлов при помощи функции ReleaseFileSet() и удалит саму группу, вызывая функцию ClearFileSet(). Файлы, разумеется, не удаляются с диска, удаляется только группа путей к файлам, которая использовалась для блокирования. // ===================================================
Программа LOGLOCK
5.3.1. Программа LOGLOCK
Программа LOGLOCK (листинг 24) демонстрирует использование логических записей для синхронизации процессов.
Вначале программа запрашивает в цикле у оператора имена логических записей и добавляет их в набор. Цикл добавления записей завершается, когда оператор вводит символ "-". Затем программа выполняет попытку заблокировать группу записей.
Вы можете запустить эту программу одновременно на двух рабочих станциях. При попытке заблокировать уже заблокированную ранее логическую запись программа перейдет в состояние ожидания.
Перед завершением своей работы программа LOGLOCK разблокирует записи и удалит набор. // ===================================================
Программа PHYSLOCK
5.2.1. Программа PHYSLOCK
Для иллюстрации блокирования физических записей файла мы составили две программы - PHYSLOCK (листинг 22) и RECACC (листинг 23). Первая программа вводит с консоли имена файлов, смещения и размеры записей.
Все введенные файлы открываются, так как для добавления записей нужен индекс файлов. Затем записи блокируются функцией LockPhysicalRecordSet(), после чего программа переходит в состояние ожидания и находится в нем до тех пор, пока оператор не нажмет любую клавишу. В это время записи заблокированы. С помощью программы RECACC вы можете попытаться выполнить запись в заблокированные или свободные записи.
Когда оператор нажмет клавишу, программа PHYSLOCK разблокирует все записи и удалит набор записей. // ===================================================
Программа SEMSIGN
5.4.1. Программа SEMSIGN
Программа SEMSIGN (листинг 25) демонстрирует использование семафоров.
Эта программа открывает семафор с именем SEMLOCK, определяет его состояние. Вся информация, касающаяся семафора, выводится в стандартный поток вывода. Затем с помощью функции WaitOnSemaphore() программа запрашивает доступ к критическому ресурсу. После того как оператор нажмет любую клавишу, программа вызывает функцию SignalSemaphore(), освобождающую ресурс, и закрывает семафор.
Вы можете запустить эту программу сначала на одной станции. Затем, после того как критический ресурс будет получен, запустите эту же программу на другой станции. Так как первая программа еще не освободила ресурс, вторая не сможет получить к нему доступа.
Если вы завершите работу первой программы в течение 20 секунд, вторая программа получит доступ к ресурсу, если нет - она завершится с сообщением о том, что ресурс занят. // ===================================================
Программа RECACC
5.2.2. Программа RECACC
Программа RECACC (листинг 23) предназначена для работы вместе с программой PHYSLOCK. Она запрашивает с консоли путь к файлу, а также смещение области памяти, в которую затем будет записана небольшая текстовая строка. Если эта область окажется заблокированной, программа завершается сообщением об ошибке. // ===================================================
Блокирование файлов
В этом разделе мы рассмотрим методы синхронизации работы программ, основанные на блокировании файлов.
Принцип блокирования файлов достаточно прост. Например, в MS-DOS, если запущена программа SHARE.EXE, программа, открывая файл, может указать, что этот файл будет использоваться ей монопольно. При попытке открыть этот файл еще раз другая программа получит от соответствующей функции MS-DOS код ошибки.
Для открывания файлов из программы, составленной на языке программирования C (или C++), удобно использовать функцию open(): int open(const char *path, int access [, unsigned mode]);
Для использования этой функции ваша программа должна содержать следующие две строки: #include <fcntl.h> #include <sys\stat.h>
Функция возвращает индекс (handle) открытого файла или -1 в случае ошибки.
Параметр path указывает путь к открываемому файлу.
Параметр access определяет режим доступа к открываемому файлу. Вы можете использовать символические константы (их можно объединять при помощи логической операции ИЛИ):
Константа | Значение |
O_RDONLY | Открыть файл только для чтения |
O_WRONLY | Открыть файл только для записи |
O_RDWR | Открыть файл для чтения и записи |
O_APPEND | Добавлять записываемые данные в конец файла |
O_CREAT | Создать файл и открыть его. Если файл уже существует, ничего не происходит. Если файл не существует, он создается и открывается |
O_EXCL | Этот режим используется только вместе с режимом O_CREAT. Если файл уже существует, возвращается признак ошибки |
O_TRUNC | Открыть файл и установить для него нулевую длину |
O_BINARY | Файл открывается в двоичном режиме |
O_TEXT | Файл открывается в текстовом режиме. Для него выполняется преобразование байтов CR-LF в '\n' |
O_DENYNONE | К файлу разрешен множественный доступ со стороны нескольких программ, т. е. этот файл может быть открыт несколько раз |
O_DENYALL | Этот файл может быть открыт только один раз. Если другая программа попытается открыть файл для чтения или для записи, она получит признак ошибки |
O_DENYWRITE | Другая программа не может открыть этот файл еще раз для записи, но она может открыть его для чтения |
O_DENYREAD | Другая программа не может открыть этот файл еще раз для чтения, но она может открыть его для записи |
Необязательный параметр mode указывается только для вновь создаваемых файлов (в режиме O_CREAT). Он может принимать следующие значения:
Константа | Значение |
S_IWRITE | Разрешена запись в файл |
S_IREAD | Разрешено чтение файла |
Эти значения можно объединять при помощи логической операции ИЛИ.
Существует еще одна функция для открытия файлов, аналогичная функции open(), - функция sopen(): int sopen(path, access, shflag, mode);
Для использования этой функции в программу необходимо включить следующие строки: #include <fcntl.h> #include <sys\stat.h> #include <share.h> #include <IO.H>
Параметры этой функции аналогичны параметрам функции open(). Дополнительный параметр shflag может принимать следующие значения:
Константа | Значение |
SH_COMPAT | Режим совместимости. Другие программы могут открывать файл, открытый в режиме совместимости, однако они также должны открывать его именно в режиме совместимости |
SH_DENYNONE | Другим программам разрешается открывать этот файл для записи и чтения, но не в режиме совместимости |
SH_DENYRD | Другие программы могут открывать этот файл, но только на запись |
SH_DENYRW | Другие программы могут открывать этот файл, но только на чтение |
Приведенные выше константы нельзя объединять логической операцией ИЛИ, можно указывать только одну константу из списка.
Сетевая оболочка Novell NetWare предоставляет более удобные средства для блокирования файлов. Эти средства позволяют блокировать сразу несколько файлов, что может быть необходимо для обновления содержимого сложной базы данных.
Для того чтобы заблокировать один или несколько файлов средствами сетевой оболочки, вначале надо образовать группу, состоящую из блокируемых файлов. Для добавления файлов в группу служит функция LogFile(). Удалить файл из группы можно при помощи функции ClearFile().
После того как группа файлов создана, все эти файлы можно одновременно заблокировать при помощи функции LockFileSet(). Файлы можно блокировать и во время добавления их в группу.
Функция LogFile() имеет следующий прототип: int LogFile(char *FileName, BYTE LockDirective,WORD Timeout);
Параметр FileName задает путь к файлу, который необходимо добавить в группу.
Параметр LockDirective определяет, надо ли блокировать файл сразу после его добавления в группу:
0x00 | Файл добавляется в группу, но не блокируется |
0x01 | Добавляемый файл блокируется для использования заблокировавшей его программой в монопольном режиме |
0x03 | Добавляемый файл блокируется для совместного использования |
Параметр Timeout определяет период времени (в 18-х долях секунды), в течение которого файл-сервер будет ожидать, если файл нельзя заблокировать немедленно. Если для этого параметра задать нулевое значение, ожидание выполняться не будет.
Функция возвращает ноль при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на файл-сервере |
0xFE | Истек период ожидания, заданный параметром Timeout, но файл так и не удалось заблокировать |
0xFF | Сбой при блокировании файла |
Для удаления файла из группы можно использовать функцию ClearFile(): int ClearFile(char *FileName);
Единственный параметр функции указывает путь к файлу, удаляемому из общего списка файлов, предназначенных для блокирования. Функция возвращает нулевое значение или значение 0xFF, если файла с указанным путем нет в списке.
Функция ClearFileSet() позволяет разблокировать все файлы группы и удалить группу: void ClearFileSet(void);
Прототип функции LockFileSet(), используемой для блокирования группы файлов: int LockFileSet(WORD Timeout);
Параметр Timeout используется так же, как и при вызове функции LogFile().
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Истек период ожидания, заданный параметром Timeout, но файл так и не удалось заблокировать |
0xFF | Сбой при блокировании файла |
После того как группа файлов заблокирована, вы можете разблокировать отдельные файлы или всю группу сразу.
Для разблокирования отдельных файлов используйте функцию ReleaseFile(): int ReleaseFile(char *FileName);
Параметр FileName указывает путь к разблокируемому файлу. Функция возвращает нулевое значение или значение 0xFF, если файла с указанным путем нет в списке.
Если вам надо разблокировать сразу все файлы, добавленные в группу, используйте функцию ReleaseFileSet(): void ReleaseFileSet(void);
Для добавления файлов в группу вместо функции LogFile() можно использовать функцию EBh прерывания INT21h:
На входе: | AH | = | EBh; |
AL | = | Параметр LockDirective; | |
BP | = | Параметр Timeout.; | |
DS:DX | = | Адрес буфера, в котором находится путь к добавляе-мому файлу в формате текстовой строки, закрытой двоичным нулем. | |
На выходе: | AL | Код ошибки или 0, если операция завершилась без ошибок. |
Для удаления файла из списка вместо функции ClearFile() можно использовать функцию EDh прерывания INT 21h:
На входе: | AH | = | EDh; |
DS:DX | = | Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для удаления группы файлов и разблокирования всех файлов вместо функции ClearFileSet() можно использовать функцию CFh прерывания INT 21h:
На входе: | AH | = | CFh; |
DS:DX | = | Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для блокирования группы файлов вместо функции LockFileSet() можно использовать функцию CBh прерывания INT 21h:
На входе: | AH | = | CBh; |
AL | = | Регистр должен содержать нулевое значение; | |
BP | = | Параметр Timeout. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для разблокирования файла вместо функции ReleaseFile() можно использовать функцию ECh прерывания INT 21h:
На входе: | AH | = | ECh; |
DS:DX | = | Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для разблокирования группы файлов вместо функции ReleaseFileSet() можно использовать функцию CDh прерывания INT 21h:
На входе: | AH | = | CDh. |
На выходе: | Регистры не используются. |
Блокирование физических записей
Если вы разрабатываете СУБД с коллективным доступом к файлам базы данных, расположенным на сервере, метод блокирования файлов может оказаться не слишком удобным. Так как разные пользователи в разные моменты времени работают с различными участками (записями) базы данных, едва ли стоит блокировать весь файл, если один из пользователей решил изменить содержимое только одной записи в базе данных. Было бы лучше заблокировать только эту запись.
Средства сетевой оболочки позволяют вам блокировать отдельные записи в разных файлах, образуя группу блокируемых записей (по аналогии с группой файлов, рассмотренной нами в предыдущем разделе). В группу могут входить записи из одного или нескольких файлов. Каждая запись идентифицируется индексом файла (файл необходимо открыть функцией open() или аналогичными средствами), смещением начала записи в байтах от начала файла и размером записи в байтах.
Для создания группы физических записей используется функция LogPhysicalRecord(), аналогичная по назначению функции LogFile(), но работающая с записями. Удалить запись из группы можно функцией ClearPhysicalRecord(). Вся группа записей удаляется функцией ClearPhysicalRecordSet().
Записи можно блокировать сразу при их добавлении в группу либо позже. Вы можете заблокировать сразу все записи, относящиеся к группе, вызвав функцию LockPhysicalRecordSet().
Для разблокирования записи используется функция ReleasePhysicalRecord(). Если надо разблокировать сразу все записи, вызывайте функцию ReleasePhysicalRecordSet().
Функция LogPhysicalRecord() имеет следующий прототип: int LogPhysicalRecord(int FileHandle, long RecordStartOffset, long RecordLength, BYTE LockDirective,WORD Timeout);
Параметр FileHandle задает индекс файла, которому принадлежит блокируемая запись.
Параметры RecordStartOffset и RecordLength задают смещение от начала файла и размер блокируемой записи в байтах.
Параметр LockDirective определяет, надо ли блокировать запись сразу после его добавления в группу:
0x00 | Запись добавляется в группу, но не блокируется |
0x01 | Добавляемая запись блокируется для использования заблокировавшей его программой в монопольном режиме |
0x03 | Добавляемая запись блокируется для совместного использования |
Параметр Timeout определяет период времени (в 18-х долях секунды), в течение которого файл-сервер будет ожидать, если запись нельзя заблокировать немедленно. Если для этого параметра задать нулевое значение, ожидание выполняться не будет.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на файл-сервере |
0xFE | Истек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать |
0xFF | Сбой при блокировании записи |
Для удаления записи из группы можно использовать функцию ClearPhysicalRecord(): int ClearPhysicalRecord(int FileHandle, long RecordStartOffset, long RecordLength);
Параметры этой функции аналогичны параметрам функции LogPhysicalRecord. Функция возвращает нулевое значение или значение 0xFF, если в списке нет указанной записи.
Функция ClearPhysicalRecordSet() позволяет разблокировать все записи группы и удалить группу void ClearPhysicalRecordSet(void);
Прототип функции LockPhysicalRecordSet(), используемой для блокирования группы записей: int LockPhysicalRecordSet(BYTE LockDirective, WORD Timeout);
Параметр LockDirective задает режим блокирования. Если он равен 0, записи блокируются для монопольного использования программой, заблокировавшей записи. Если параметр имеет значение 1, записи блокируются для совместного использования в режиме чтения.
Параметр Timeout используется так же, как и при вызове функции LogPhysicalRecord().
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Истек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать |
0xFF | Сбой при блокировании записи |
После того как группа записей заблокирована, вы можете разблокировать отдельные записи или всю группу сразу.
Для разблокирования отдельных записей используйте функцию ReleasePhysicalRecord(): int ReleasePhysicalRecord(int FileHandle, long RecordStartOffset, long RecordLength);
Параметры задают индекс файла, смещение записи и ее длину. Функция возвращает нулевое значение или значение 0xFF, если указанной записи нет в списке.
Если надо разблокировать сразу все записи, добавленные в группу, используйте функцию ReleasePhysicalRecordSet(): void ReleasePhysicalRecordSet(void);
Для добавления записей в группу вместо функции LogPhysicalRecord() можно использовать функцию BCh прерывания INT21h:
На входе: | AH | = | BCh; |
AL | = | Параметр LockDirective; | |
BP | = | Параметр Timeout; | |
BX | = | Индекс файла; | |
CX | = | Старшее слово смещения записи относительно начала файла; | |
DX | = | Младшее слово смещения; | |
SI | = | Длина записи. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для удаления записи из списка вместо функции ClearPhysicalRecord() можно использовать функцию BEh прерывания INT 21h:
На входе: | AH | = | BEh; |
BX | = | Индекс файла; | |
CX | = | Старшее слово смещения записи относительно начала файла; | |
DX | = | Младшее слово смещения. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для удаления группы записей и разблокирования всех записей вместо функции ClearPhysicalRecordSet() можно использовать функцию C4h прерывания INT 21h:
На входе: | AH | = | C4h. |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для блокирования группы записей вместо функции LockPhysicalRecordSet() можно использовать функцию C2h прерывания INT 21h:
На входе: | AH | = | C2h; |
AL | = | Параметр LockDirective; | |
BP | = | Параметр Timeout. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для разблокирования записи вместо функции ReleasePhysicalRecord() можно использовать функцию BDh прерывания INT 21h:
На входе: | AH | = | BDh; |
BX | = | Индекс файла; | |
CX | = | Старшее слово смещения записи относительно начала файла; | |
DX | = | Младшее слово смещения. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для разблокирования группы записей вместо функции ReleasePhysicalRecordSet() можно использовать функцию C3h прерывания INT 21h:
На входе: | AH | = | C3h. |
На выходе: | = | Регистры не используются. |
Блокирование логических записей
Для синхронизации процессов можно использовать не только физическое блокирование записей файлов, когда программа не сможет изменить их содержимое, как бы она ни старалась, но и так называемое блокирование логических записей. Логическая запись реально не существует в виде записи на диске, у нее есть только имя. Пользуясь этим именем, программа может блокировать и разблокировать логическую запись. Если запись уже была заблокирована одним процессом, второй процесс не сможет ее заблокировать до тех пор, пока первый процесс не разблокирует данную запись.
Средства сетевой оболочки позволяют создавать группы логических записей и блокировать их все вместе, по аналогии с группами блокируемых файлов и физических записей.
Логика использования логических записей проста. С каждой критичной частью, например базы данных, связывается группа имен, т. е. логических записей. Когда программа желает изменить содержимое этой критической части базы данных, она пытается заблокировать соответствующие логические записи. Если никакой другой процесс в сети не изменяет те же самые данные и уже не заблокировал данную группу логических записей, наша программа сможет заблокировать группу для себя.
Выполнив блокировку логических записей, программа выполняет все необходимые действия с файлами и затем разблокирует логические записи, предоставляя доступ к данным другим процессам.
Необходимо отметить, что при синхронизации процессов с помощью логических записей (а также семафоров, которые мы рассмотрим ниже) программы сами должны проверять состояние записей и правильно выполнять доступ к файлам, так как физически данные в файлах не блокируются.
Набор функций, используемый для работы с логическими записями, аналогичен набору функций для работы с физическими записями. Однако в отличие от физических записей, которые связаны с файлами и идентифицируются индексом файла, смещением и размером, логические записи идентифицируются по имени.
Для создания группы логических записей используется функция LogLogicalRecord(). Удалить запись из группы можно функцией ClearLogicalRecord(). Вся группа записей удаляется функцией ClearLogicalRecordSet().
Записи можно блокировать сразу при их добавлении в группу либо можно заблокировать сразу все записи, относящиеся к группе, вызвав функцию LockLogicalRecordSet().
Для разблокирования логической записи используется функция ReleaseLogicalRecord(). Если надо разблокировать сразу все логические записи, вызывайте функцию ReleaseLogicalRecordSet().
Функция LogLogicalRecord() имеет следующий прототип: int LogLogicalRecord(char LogicalRecordName, BYTE LockDirective,WORD Timeout);
Параметр LogicalRecordName задает имя логической записи, добавляемой в группу блокируемых записей. Имя может иметь длину до 100 байт и должно быть в формате текстовой строки, закрытой двоичным нулем.
Параметр LockDirective определяет, надо ли блокировать запись сразу после ее добавления в группу:
0x00 | Запись добавляется в группу, но не блокируется |
0x01 | Добавляемая запись блокируется для использования заблокировавшей его программой в монопольном режиме |
0x03 | Добавляемая запись блокируется для совместного использования |
Параметр Timeout определяет период времени (в 18-x долях секунды), в течение которого файл-сервер будет ожидать, если запись нельзя заблокировать немедленно. Если для этого параметра задать нулевое значение, ожидание выполняться не будет.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на файл-сервере |
0xFE | Истек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать |
0xFF | Сбой при блокировании записи |
Для удаления записи из группы можно использовать функцию ClearLogicalRecord(): int ClearLogicalRecord(char LogicalRecordName);
Параметр этой функции задает имя логической записи, удаляемой из группы. Функция возвращает нулевое значение или значение 0xFF, если в группе нет указанной записи.
Функция ClearLogicalRecordSet() позволяет разблокировать все записи группы и удалить группу: void ClearLogicalRecordSet(void);
Прототип функции LockLogicalRecordSet(), используемой для блокирования группы записей: int LockLogicalRecordSet(WORD Timeout);
Параметр Timeout используется так же, как и при вызове функции LogLogicalRecord().
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Истек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать |
0xFF | Сбой при блокировании записи |
После того как группа записей заблокирована, вы можете разблокировать отдельные записи или всю группу сразу.
Для разблокирования отдельных записей используйте функцию ReleaseLogicalRecord(): int ReleaseLogicalRecord(char LogicalRecordName);
Параметр задает имя записи. Функция возвращает нулевое значение или значение 0xFF, если указанной записи нет в группе.
Если надо разблокировать сразу все записи, добавленные в группу, используйте функцию ReleaseLogicalRecordSet(): void ReleaseLogicalRecordSet(void);
Для добавления записей в группу вместо функции LogLogicalRecord() можно использовать функцию D0h прерывания INT21h:
На входе: | AH | = | D0h; |
AL | = | Параметр LockDirective; | |
BP | = | Параметр Timeout; | |
DS:DX | = | Адрес имени логической записи. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для удаления записи из списка вместо функции ClearLogicalRecord() можно использовать функцию D4h прерывания INT 21h:
На входе: | AH | = | D4h; |
DS:DX | = | Адрес имени логической записи. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для удаления группы записей и разблокирования всех записей вместо функции ClearLogicalRecordSet() можно использовать функцию D5h прерывания INT 21h:
На входе: | AH | = | D5h. |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для блокирования группы записей вместо функции LockLogicalRecordSet() можно использовать функцию D1h прерывания INT 21h:
На входе: | AH | = | D1h; |
AL | = | Регистр должен содержать значение 0; | |
BP | = | Параметр Timeout. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для разблокирования записи вместо функции ReleaseLogicalRecord() можно использовать функцию D2h прерывания INT 21h:
На входе: | AH | = | D2h; |
DS:DX | = | Адрес имени логической записи; | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для разблокирования группы записей вместо функции ReleaseLogicalRecordSet() можно использовать функцию D3h прерывания INT 21h:
На входе: | AH | = | D3h. |
На выходе: | Регистры не используются. |
Семафоры
Последнее средство синхронизации процессов, которое мы рассмотрим в этой главе, - семафоры. О семафорах мы уже говорили в томе "Библиотеки системного программиста", посвященном защищенному режиму работы процессоров. Семафоры Novell NetWare - это ресурсы, расположенные физически на файл-сервере.
Программа может открыть (создать) семафор с помощью функции OpenSemaphore(), указав его имя. Функция, открывающая семафор, возвращает индекс семафора, который используется для выполнения всех операций над семафором.
С семафором помимо имени связывается некоторое число, которое может находиться в диапазоне от -127 до 127. Это число называется значением семафора.
Кроме того, для каждого семафора имеется счетчик процессов, открывших семафор. Этот счетчик увеличивает свое значение на 1, когда очередная программа открывает семафор функцией OpenSemaphore(), и уменьшает на единицу, когда одна из программ закрывает семафор функцией CloseSemaphore(). Когда счетчик принимает нулевое значение, семафор уничтожается.
Перед использованием критического ресурса, с которым связан семафор, программа должна уменьшить значение семафора, вызвав функцию WaitOnSemaphore(). Если значение семафора равно нулю или больше нуля, программа может использовать ресурс. Если же семафор имеет отрицательное значение, функция WaitOnSemaphore() ожидает семафор в течение указанного ей времени.
После завершения работы с критическим ресурсом программа должна увеличить значение семафора, вызвав функцию SignalSemaphore().
Начальное значение семафора задается при его создании и обычно равно единице.
Приведем прототип функции OpenSemaphore(), открывающей семафор: int OpenSemaphore(char *SemaphoreName, int InitialValue, long *SemaphoreHandle, WORD *OpenCount);
Параметр SemaphoreName определяет имя открываемого семафора. Имя должно иметь длину не более 127 символов, включая закрывающий строку двоичный ноль.
Параметр InitialValue задает значение семафора, но только при первом открытии, т. е. при создании семафора. Если семафор уже создан другим процессом, этот параметр игнорируется. В качестве начального значения вы можете использовать единицу.
Параметр SemaphoreHandle - указатель на переменную, в которую будет записан индекс открытого семафора. Этот индекс необходим для выполнения всех операций с семафором.
Параметр OpenCount - счетчик использования семафора. Когда очередной процесс открывает данный семафор, счетчик увеличивает свое значение на единицу.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Неправильная длина имени семафора |
0xFF | Неправильное начальное значение семафора |
Для того чтобы закрыть семафор, вам необходимо использовать функцию CloseSemaphore(): int CloseSemaphore(long SemaphoreHandle);
В качестве параметра этой функции указывается индекс закрываемого семафора.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFF | Неправильное значение индекса семафора |
С помощью функции ExamineSemaphore() вы можете узнать текущее состояние семафора: int ExamineSemaphore(long SemaphoreHandle, int *SemaphoreValue, WORD *OpenCount);
Для заданного первым параметра семафора функция возвращает значение семафора (параметр SemaphoreValue) и счетчик использования (параметр OpenCount).
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFF | Неправильное значение индекса семафора |
Перед использованием критического ресурса программа должна вызвать функцию WaitOnSemaphore(), уменьшающую значение семафора: int WaitOnSemaphore(long SemaphoreHandle, WORD Timeout);
Параметр SemaphoreHandle определяет используемый семафор.
С помощью параметра Timeout определяется время, в течение которого функция ожидает доступность ресурса (в 18-х долях секунды).
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Истекло время ожидания, заданное параметром Timeout |
0xFF | Неправильное значение индекса семафора |
Функция SignalSemaphore(), увеличивающая значение семафора, имеет следующий прототип: int SignalSemaphore(long SemaphoreHandle);
Индекс семафора задается параметром функции.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x01 | Переполнение семафора, значение семафора стало больше 127 |
0xFF | Неправильное значение индекса семафора |
Для работы с семафорами можно использовать функцию C5h прерывания INT21h. В зависимости от содержимого регистра AL эта функция выполняет ту или иную операцию с семафором.
Открытие семафора:
На входе: | AH | = | C5h; |
AL | = | 00h; | |
DS:DX | = | Адрес имени семафора; | |
CL | = | Начальное значение семафора. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Определение состояния семафора:
На входе: | AH | = | C5h; |
AL | = | 01h; | |
CX,DX | = | Индекс семафора; | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок; |
CX | = | Значение семафора; | |
DL | = | Счетчик использований семафора. |
Уменьшение значения семафора:
На входе: | AH | = | C5h; |
AL | = | 02h; | |
CX,DX | = | Индекс семафора; | |
BP | = | Время ожидания. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Увеличение значения семафора:
На входе: | AH | = | C5h; |
AL | = | 03h; | |
CX,DX | = | Индекс семафора. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Закрытие семафора:
На входе: | AH | = | C5h; |
AL | = | 04h; | |
CX,DX | = | Индекс семафора. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Создаем набор файлов, которые будут
Листинг 21
Блокирование файлов // Файл flock\flock.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int LogFile(char *, BYTE, WORD);
extern "C" int LockFileSet(WORD);
extern "C" void ReleaseFileSet(void);
extern "C" void ClearFileSet(void);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; char FilePath[255]; int ccode; printf("\n*FLOCK* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Создаем набор файлов, которые будут заблокированы for(;;) { printf("\nВведите путь к файлу или '-':");
gets(FilePath);
strupr(FilePath);
if(FilePath[0] == '-') break; // Добавляем файл в набор ccode = LogFile(FilePath, 0, 0);
if(!ccode) printf("Файл %s добавлен к списку\n", FilePath);
else printf("Ошибка при добавлении %02.2X\n", ccode);
} // Блокируем набор файлов ccode = LockFileSet(0);
if(!ccode) printf("Файлы заблокированы\n");
else printf("Ошибка при блокировании " "файлов %02.2X\n", ccode);
printf("Для разблокирования файлов нажмите любую клавишу\n");
getch();
// Разблокируем набор файлов ReleaseFileSet();
// Удаляем набор файлов ClearFileSet();
}
Листинг 22
Блокирование физических записей файлов // Файл physlock\physlock.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <fcntl.h>
#include <io.h>
#include <sys\stat.h>
#include <share.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int LogPhysicalRecord(int, long, long, BYTE, WORD);
extern "C" int LockPhysicalRecordSet(BYTE, WORD);
extern "C" void ReleasePhysicalRecordSet(void);
extern "C" void ClearPhysicalRecordSet(void);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; char FilePath[255]; int FileHandle; char Buff[80]; long RecordStartOffset; long RecordLength; int ccode; printf("\n*PHYSLOCK* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Создаем набор записей файлов, которые будут заблокированы for(;;) { printf("\nВведите путь к файлу или '-':");
gets(FilePath);
strupr(FilePath);
if(FilePath[0] == '-') break; // Открываем файл, в котором мы будем блокировать физические записи if ((FileHandle = open(FilePath, O_RDWR | O_BINARY | O_DENYNONE, SH_DENYNONE)) == -1) { printf("Не могу открыть файл\n");
continue; } // Задаем начало и размер блокируемой области файла printf("\nВведите смещение начала записи:");
gets(Buff);
RecordStartOffset = atol(Buff);
printf("\nВведите размер записи:");
gets(Buff);
RecordLength = atol(Buff);
// Добавляем запись в набор ccode = LogPhysicalRecord(FileHandle, RecordStartOffset, RecordLength, 0, 0);
if(!ccode) printf("Файл %s добавлен к списку\n", FilePath);
else printf("Ошибка при добавлении %02.2X\n", ccode);
} // Блокируем набор файлов ccode = LockPhysicalRecordSet(0, 0);
if(!ccode) printf("Записи файлов заблокированы\n");
else printf("Ошибка при блокировании " "записей файлов %02.2X\n", ccode);
printf("Для разблокирования записей " "файлов нажмите любую клавишу\n");
getch();
// Разблокируем набор файлов ReleasePhysicalRecordSet();
// Удаляем набор файлов ClearPhysicalRecordSet();
// Закрываем файл close(FileHandle);
}
Листинг 23
Проверка возможности получения // доступа к физическим записям файла // Файл recacc\recacc.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <fcntl.h>
#include <io.h>
#include <dos.h>
#include <sys\stat.h>
#include <share.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int LogPhysicalRecord(int, long, long, BYTE, WORD);
extern "C" int LockPhysicalRecordSet(BYTE, WORD);
extern "C" void ReleasePhysicalRecordSet(void);
extern "C" void ClearPhysicalRecordSet(void);
void main(void) { char FilePath[255]; int FileHandle; char Buff[80]; long RecordStartOffset; char msg[] = "PATCH!!!"; int ccode; unsigned count; printf("\n*RECACC* (C) Frolov A., 1993\n");
// Вводим имя файла и открываем его на запись и чтение printf("\nВведите путь к файлу:");
gets(FilePath);
strupr(FilePath);
if ((FileHandle = open(FilePath, O_RDWR | O_BINARY | O_DENYNONE, SH_DENYNONE)) == -1) { printf("Не могу открыть файл\n");
} // Задаем смещение в файле, начиная с которого // в файл будет записана строка "PATCH!!!" printf("\nВведите смещение начала записи:");
gets(Buff);
RecordStartOffset = atol(Buff);
// Позиционируем на начало записи lseek(FileHandle, RecordStartOffset, 0);
// Делаем попытку изменить содержимое записи ccode = _dos_write(FileHandle, msg, strlen(msg), &count);
if(!ccode) printf("Запись обновлена\n");
else printf("Ошибка при обновлении " "записи в файле: %02.2X\n", ccode);
// Закрываем файл close(FileHandle);
}
Листинг 24
Блокирование логических записей // Файл loglock\loglock.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int LogLogicalRecord(char *, BYTE, WORD);
extern "C" int LockLogicalRecordSet(WORD);
extern "C" void ReleaseLogicalRecordSet(void);
extern "C" void ClearLogicalRecordSet(void);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; char LogicalRecordName[100]; int ccode; printf("\n*LOGLOCK* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Создаем набор логических записей, которые будут заблокированы for(;;) { printf("\nВведите имя логической записи или '-':");
gets(LogicalRecordName);
if(LogicalRecordName[0] == '-') break; // Добавляем логическую запись в набор ccode = LogLogicalRecord(LogicalRecordName, 0, 0);
if(!ccode) printf("Логическая запись %s добавлена к списку\n", LogicalRecordName);
else printf("Ошибка при добавлении %02.2X\n", ccode);
} // Блокируем набор логических записей ccode = LockLogicalRecordSet(0);
if(!ccode) printf("Логические записи заблокированы\n");
else printf("Ошибка при блокировании " "логических записей %02.2X\n", ccode);
printf("Для разблокирования логических записей " " нажмите любую клавишу\n");
getch();
// Разблокируем набор логических записей ReleaseLogicalRecordSet();
// Удаляем набор логических записей ClearLogicalRecordSet();
}
Листинг 25
Работа с семафорами // Файл semsign\semsign.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#define BYTE unsigned char #define WORD unsigned int extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int OpenSemaphore(char *, int, long *, WORD *);
extern "C" int CloseSemaphore(long);
extern "C" int ExamineSemaphore(long, int *, WORD *);
extern "C" int SignalSemaphore(long);
extern "C" int WaitOnSemaphore(long, WORD);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int ccode; long SemaphoreHandle; WORD OpenCount; int SemaphoreValue; printf("\n*SEMSIGN* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Открываем семафор с именем SEMLOCK ccode = OpenSemaphore("SEMLOCK", 1, &SemaphoreHandle, &OpenCount);
if(!ccode) { printf("Семафор SEMLOCK открыт\n");
printf("Handle = %ld, OpenCount = %d\n", SemaphoreHandle, OpenCount);
} else { printf("Ошибка при открытии семафора " "SEMLOCK %02.2X\n", ccode);
return; } // Определяем текущее состояние семафора ccode = ExamineSemaphore(SemaphoreHandle, &SemaphoreValue, &OpenCount);
if(!ccode) { printf("SemaphoreValue = %d\n", SemaphoreValue);
} else printf("Ошибка при получении состояния семафора " "SEMLOCK %02.2X\n", ccode);
// Запрашиваем доступ к критическому ресурсу, // ожидаем получение доступа в течение 20 секунд printf("Запрашиваем доступ к критическому ресурсу...\n");
ccode = WaitOnSemaphore(SemaphoreHandle, 18*20);
if(!ccode) { printf("Доступ к критическому ресурсу получен\n");
} else { printf("Ресурс заблокирован, ошибка %02.2X\n", ccode);
return; } // Теперь, если мы получили доступ к ресурсу, можно выполнять с ним // необходимые действия. В нашей программе мы ничего не делаем, // просто ожидаем, пока оператор не нажмет любую клавишу printf("Нажмите любую клавишу для освобожения ресурса\n");
getch();
// Освобождаем ресурс ccode = SignalSemaphore(SemaphoreHandle);
if(!ccode) { printf("Ресурс освобожден\n");
} else printf("Ошибка при освобождении ресурса %02.2X\n", ccode);
// Закрываем семафор ccode = CloseSemaphore(SemaphoreHandle);
if(!ccode) printf("Семафор SEMLOCK закрыт\n");
else printf("Ошибка при закрытии семафора " "SEMLOCK %02.2X\n", ccode);
}
Объекты, коды объектов и права доступа
6.1. Объекты, коды объектов и права доступа
Файл-сервер Novell NetWare содержит базу данных объектов, в которой есть сведения о ресурсах, доступных в сети (файл-серверы, серверы печати и т. п.), о пользователях и группах пользователей и т. д. Эта база данных называется Bindery. Физически она находится в двух скрытых файлах с именами net$bind.sys и net$bval.sys, расположенных в каталоге SYS:SYSTEM.
Каждый объект, хранящийся в базе данных, имеет свое имя, уникальный для данного файл-сервера идентификатор, тип, байт доступа. Кроме того, объект может быть статическим или динамическим.
Например, каждый пользователь, добавленный супервизором сети или администратором, становится объектом, имеющим имя и идентификатор. Тип такого объекта соответствует значению 1, и это статический объект. Статический объект хранится в базе до тех пор, пока он не будет удален явным образом.
Если в сети имеются несколько файл-серверов, то для каждого сервера в базе Bindery имеются объекты, соответствующие всем серверам, активным в сети. Это временные объекты, которые удаляются автоматически, когда соответствующий файл-сервер прекращает свою работу. Вы можете воспользоваться этим обстоятельством для получения списка активных серверов, имеющихся в сети.
Программы пользователя могут не только считывать содержимое базы объектов, но и добавлять свои собственные записи, а также редактировать имеющиеся. Для сокращения объема книги мы рассмотрим только способы извлечения информации из базы Bindery. Полностью работа с базой данных объектов описана в документации, поставляющейся вместе с библиотекой Novell NetWare C Interface.
Объекты, коды объектов и права доступаПрограмма BACCESS
Листинг 26
Просмотр базы объектов
Программа BSCAN
Программа BACCESS
6.1.1. Программа BACCESS
Приведем текст программы BACCESS (листинг 26), которая определяет и выводит на экран имя, идентификатор, тип объекта, а также его уровень доступа. В программе используются описанные выше функции. // ===================================================
Программа BSCAN
6.2.1. Программа BSCAN
Приведем исходный текст программы BSCAN (листинг 27), которая просматривает базу данных объектов. Для каждого найденного объекта программа выводит имя и расшифрованный тип объекта, флаг и уровень доступа. Если объект имеет дополнительные записи (properties), вызывается функция, которая выводит имена найденных записей. // ===================================================
Объекты, коды объектов и права доступа
База данных Bindery хранит объекты, которые имеют такие атрибуты, как идентификатор, имя, тип, флаг (статический или динамический объект), байт доступа.
Идентификатор объекта - число размером 4 байта, которое является уникальным для данного файл-сервера. Никакие два объекта, сведения о которых хранятся в одной базе данных на одном сервере, не могут иметь одинаковые идентификаторы.
Имя объекта представляет собой текстовую строку размером не более 47 байт. В операциях поиска объектов в базе данных по имени в поле имени допускается указывать символы "*" и "?", которые интерпретируются обычным способом, как и в MS-DOS.
Библиотека NetWare C Interface имеет функции, позволяющие просматривать список всех объектов базы данных, искать объекты определенного типа или по имени с использованием шаблона и символов "*" и "?". По идентификатору объекта вы можете легко получить его имя и наоборот, по имени можно узнать идентификатор объекта.
Тип объекта определяет сетевой ресурс и может принимать следующие значения:
Значение | Описание |
0 | Неклассифицируемый (неизвестный) объект |
1 | Пользователь |
2 | Группа пользователей |
3 | Очередь печати |
4 | Файл-сервер |
5 | Сервер заданий |
6 | Шлюз |
7 | Сервер печати |
8 | Очередь для архивирования |
9 | Сервер для архивирования |
A | Очередь заданий |
B | Администратор |
24 | Сервер удаленного моста |
Флаг объекта характеризует время жизни объекта. Если этот флаг равен нулю, объект статический и для его уничтожения необходимо вызывать специальную функцию. Если флаг равен единице, то это динамический объект, который удаляется автоматически, как только исчезает соответствующий сетевой ресурс.
Например, если в сети имеется несколько файл-серверов, в базе каждого сервера имеются объекты, описывающие все активные серверы сети. Как только один из серверов прекращает свою работу, из всех баз данных, расположенных на остальных файл-серверах, удаляются объекты, описывающие завершивший работу файл-сервер.
Байт доступа используется для определения прав, необходимых для поиска, чтения, создания, редактирования или удаления объекта. Две тетрады байта отвечают за доступ на чтение и доступ на запись. Младшие четыре бита байта доступа отвечают за чтение, старшие - за запись.
Для тетрад определены значения от 0 до 4 - уровни доступа. Для того чтобы пользователь или другой объект получили доступ, тетрады его собственного байта доступа должны иметь значения, равные или превышающие значения в байте доступа объекта, к которому запрашивается доступ.
Приведем список возможных значений для уровней доступа:
0 | Anyone | Объект не подключен к файл-серверу |
1 | Logged | Объект подключен к файл-серверу |
2 | Object | Объект подключен к файл-серверу с именем и паролем |
3 | Supervisor | Объект имеет права супервизора |
4 | NetWare | Объект имеет права операционной системы Novell NetWare |
Для определения собственного уровня доступа и идентификатора пользователя программа может воспользоваться функцией GetBinderyAccessLevel(): int GetBinderyAccessLevel(BYTE *SecurityAccessLevel, long *ObjectID);
Первый параметр этой функции указывает на слово, в которое будет записан уровень доступа, второй - на двойное слово, в которое будет записан идентификатор пользователя.
Вместо функции GetBinderyAccessLevel() можно использовать функцию E3h прерывания INT21h:
На входе: | AH | = | E3h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 70 };
Буфер ответа имеет следующий формат: struct REPLAY { WORD PacketLength; // размер пакета BYTE SecurityAccessLevel; // уровень доступа long ObjectID; // идентификатор объекта };
По идентификатору объекта вы можете получить его имя и тип с помощью функции GetBinderyObjectName(): int GetBinderyObjectName(long ObjectID, char *ObjectName, WORD *ObjectType);
Для объекта, идентификатор которого задан первым параметром, функция возвращает имя объекта и его тип, записывая их в области памяти, указанные при помощи второго и третьего параметров.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на сервере |
0xFC | В базе нет объекта с указанным идентификатором |
0xFE | База данных Bindery заблокирована |
0xFF | Сбой базы данных Bindery |
Вместо функции GetBinderyObjectName() вы также можете использовать функцию E3h прерывания INT 21h. При этом необходимо использовать форматы буфера запроса и буфера ответа, приведенные ниже.
Буфер запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 54 long ObjectID; // идентификатор объекта };
Буфер ответа: struct REPLAY { WORD PacketLength; // размер пакета long ObjectID; // идентификатор объекта WORD ObjectType; // тип объекта BYTE ObjectName[48]; // имя объекта };
Для получения идентификатора объекта по его имени и типу вы можете воспользоваться функцией GetBinderyObjectID(): int GetBinderyObjectID(char *ObjectName,WORD ObjectType, long *ObjectID);
Имя и тип объекта задаются первым и вторым параметрами, идентификатор записывается по адресу, заданному третьим параметром.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на сервере |
0xEF | Имя объекта указано неправильно |
0xF0 | Не допускаются символы шаблона "*", "?" |
0xFC | В базе нет объекта с указанным идентификатором |
0xFE | База данных Bindery заблокирована |
0xFF | Сбой базы данных Bindery |
Вместо функции GetBinderyObjectID() можно использовать функцию E3h прерывания INT 21h. Приведем форматы буфера запроса и буфера ответа.
Буфер запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 53 long ObjectType; // тип объекта BYTE ObjectNameLength; // длина имени объекта BYTE ObjectName[ObjectNameLength]; // имя объекта };
Буфер ответа: struct REPLAY { WORD PacketLength; // размер пакета long ObjectID; // идентификатор объекта WORD ObjectType; // тип объекта BYTE ObjectName[48]; // имя объекта };
Просмотр базы объектов
В этом разделе мы рассмотрим задачу сканирования базы данных Bindery с целью получения списка имеющихся в ней объектов. Например, вам может потребоваться список активных серверов в сети, список пользователей или список рабочих групп.
Для получения списка объектов, сведения о которых хранятся в базе данных Bindery, предназначена функция ScanBinderyObject(): int ScanBinderyObject(char *SearchObjectName, WORD SearchObjectType, long *ObjectID, char *ObjectName, WORD *ObjectType, char *ObjectHasProperties, char *ObjectFlag, char *ObjectSecurity);
Эта функция должна использоваться в цикле. При первом вызове в переменную, на которую указывает параметр ObjectID, необходимо записать значение -1. В дальнейшем в эту переменную будет записываться идентификатор найденного объекта.
Для поиска следует указать имя объекта (параметр SearchObjectName) и тип объекта (параметр SearchObjectType). В качестве имени объекта можно использовать шаблон с символами "*" и "?". Тип объекта может быть задан конкретно, либо можно указать значение -1. В последнем случае функция будет искать объекты всех типов. Для того чтобы найти все объекты всех типов, в качестве имени надо указать строку "*", в качестве типа задать значение -1.
Для найденных объектов в соответствующие переменные, указанные параметрами функции, будут записаны имя объекта (параметр ObjectName), тип объекта (параметр ObjectType), флаг (параметр ObjectFlag), байт доступа (параметр ObjectSecurity). Кроме того, в переменную, на которую указывает параметр ObjectHasProperties, записывается значение 0xFF, если объект имеет дополнительную связанную с ним информацию (Properties), которую можно извлечь специально предназначенными для этого функциями.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на сервере |
0xEF | Имя объекта указано неправильно |
0xFC | В базе нет объекта с указанным идентификатором |
0xFE | База данных Bindery заблокирована |
0xFF | Сбой базы данных Bindery |
Ваша программа должна вызывать функцию ScanBinderyObject() в цикле до тех пор, пока она не возвратит код ошибки, отличный от нуля.
Вместо функции ScanBinderyObject() можно использовать функцию E3h прерывания INT21h:
На входе: | AH | = | E3h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса имеет следующий формат: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 55 BYTE ObjectID; // идентификатор объекта WORD SearchObjectType; // тип объекта BYTE NameLength; // длина имени образца для // поиска объекта BYTE SearchObjectName[NameLength]; // имя образца для // поиска объекта };
Буфер ответа имеет следующий формат: struct REPLAY { WORD PacketLength; // размер пакета long ObjectID; // идентификатор объекта WORD ObjectType; // тип объекта BYTE ObjectName[48]; // имя объекта BYTE ObjectFlag; // флаг объекта BYTE SecurityAccessLevel; // уровень доступа BYTE ObjectHasProperties; // есть записи };
В базе данных объектов Bindery с каждым объектом может быть связано несколько дополнительных записей, содержащих данные (property). Каждая такая запись имеет свое имя, флаг и байт доступа. Если объект имеет записи, вы можете получить список их имен и других атрибутов при помощи функции ScanProperty(): int ScanProperty(char *ObjectName, WORD ObjectType, char *SearchPropertyName, long *SequenceNumber, char *PropertyName, char *PropertyFlag, char char *PropertySecurity, char *PropertyHasValue, char *MoreProperties);
Функция должна вызываться в цикле. При первом вызове переменная, на которую указывает параметр SequenceNumber, должна содержать значение -1. При последующих вызовах содержимое этой переменной будет изменяться автоматически.
Для считывания полей функции необходимо указать имя сканируемого объекта (параметр ObjectName), тип объекта (параметр ObjectType), а также имя записи или шаблон имени записи (параметр SearchPropertyName). В шаблоне можно использовать символы "*" и "?".
Для найденных записей в соответствующие переменные, указанные параметрами функции, будут записаны имя записи (параметр PropertyName), флаг записи (параметр PropertyFlag), байт доступа (параметр PropertySecurity), признак того, что запись имеет значения (параметр PropertyHasValue), признак того, что в объекте есть еще и другие записи (MoreProperties).
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на сервере |
0xF1 | Неправильный код доступа |
0xFB | Указанная запись не найдена |
0xFC | В базе нет объекта с указанным идентификатором |
0xFE | База данных Bindery заблокирована |
0xFF | Сбой базы данных Bindery |
В Novell NetWare для каждого типа объекта существует определенный набор записей, которые могут быть связаны с этим объектом. Например, с объектом типа 1 (обычный пользователь) связаны такие записи, как PASSWORD (пароль) и SECURITY_EQUALS (эквивалентность прав доступа). Содержимое записей можно считать при помощи функции ReadPropertyValue(), которая описана в документации по библиотеке NetWare C Interface. Для этого пользователь, запустивший программу, должен обладать достаточным уровнем доступа.
Приведем некоторые имена полей, определенных в NetWare:
Имя записи | Тип объекта | Доступ, запись/чтение |
BLOCKS_READ | Файл-сервер | 3/1 |
BLOCKS_WRITTEN | Файл-сервер | 3/1 |
CONNECT_TIME | Файл-сервер | 3/1 |
GROUP_MEMBERS | Группа пользователей | 3/1 |
GROUPS_I'M_IN | Пользователь | 3/1 |
IDENTIFICATION | Пользователь | 3/1 |
NET_ADDRESS | Файл-сервер | 4/0 |
OLD_PASSWORDS | Пользователь | 3/3 |
OPERATORS | Файл-сервер | 3/3 |
PASSWORDS | Пользователь | 4/4 |
SECURITY_EQUALS | Пользователь | 3/2 |
Полный список полей и подробное их описание вы найдете в документации по библиотеке NetWare C Interface.
BYTE SecurityAccessLevel; long ObjectID; char
Листинг 26
Программа для просмотра уровня // доступа рабочей станции // Файл baccess\baccess.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#define WORD unsigned int #define BYTE unsigned char extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int GetBinderyAccessLevel(BYTE *, long *);
extern "C" int GetBinderyObjectName(long, char*, WORD*);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int ccode; BYTE SecurityAccessLevel; long ObjectID; char ObjectName[48]; WORD ObjectType; printf("\n*BACCESS* (C) Frolov A., 1993\n");
// Проверяем присутствие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Получаем свой идентификатор и уровень доступа GetBinderyAccessLevel(&SecurityAccessLevel, &ObjectID);
// По идентификатору определяем свое имя ccode = GetBinderyObjectName(ObjectID, ObjectName, &ObjectType);
// Если пользователь подключился к файл-серверу, // выводим его имя, идентификатор и тип if(!ccode) { printf("Пользователь %s, ID = %lX, Type = %d\n", ObjectName, ObjectID, ObjectType);
} // Выводим права доступа на чтение printf("Права доступа на чтение:\t");
switch(SecurityAccessLevel & 0x0f) { case 0: printf("Anyone\t(не подключен к файл-серверу)\n");
break; case 1: printf("Logged\t(подключен к файл-серверу)\n");
break; case 2: printf("Object\t(подключен к файл-серверу " "с именем и паролем)\n");
break; case 3: printf("Supervisor\t(права супервизора)\n");
break; case 4: printf("NetWare\t(права Novell NetWare)\n");
break; } // Выводим права доступа на запись printf("Права доступа на запись:\t");
switch((SecurityAccessLevel >
>
4) & 0x0f) { case 0: printf("Anyone\t(не подключен к файл-серверу)\n");
break; case 1: printf("Logged\t(подключен к файл-серверу)\n");
break; case 2: printf("Object\t(подключен к файл-серверу " "с именем и паролем)\n");
break; case 3: printf("Supervisor\t(права супервизора)\n");
break; case 4: printf("NetWare\t(права Novell NetWare)\n");
break; } }
Листинг 27
Программа для просмотра содержимого // базы данных объектов // Файл bscan\bscan.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define WORD unsigned int #define BYTE unsigned char extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int ScanBinderyObject(char *, WORD, long *, char *, WORD *, char *, char *, char *);
extern "C" int ScanProperty(char *, WORD, char *, long *, char *, char *, char *, char *, char *);
void Property(char *ObjectName, WORD ObjectType);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; int ccode; BYTE ObjectSecurity; long ObjectID; char SearchObjectName[48]; char ObjectName[48]; WORD SearchObjectType; WORD ObjectType; char ObjectHasProperties; char ObjectFlag; printf("\n*BSCAN* (C) Frolov A., 1993\n");
// Проверяем присутствие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Просматриваем в цикле содержимое базы объектов, // ищем объекты всех типов SearchObjectType = -1; // Маска для поиска всех объектов strcpy(SearchObjectName, "*");
for(ObjectID = -1;;) { // Получаем очередной объект ccode = ScanBinderyObject(SearchObjectName, SearchObjectType, &ObjectID, ObjectName, &ObjectType, &ObjectHasProperties, &ObjectFlag, &ObjectSecurity);
// Если больше нет объектов или произошла ошибка, завершаем цикл if(ccode) break; // Выводим имя и тип объекта printf("\n%-18s\t", ObjectName);
switch(ObjectType) { case 0: printf("??? ");
break; case 1: printf("Пользователь ");
break; case 2: printf("Группа ");
break; case 3: printf("Очередь на печать ");
break; case 4: printf("Файл-сервер ");
break; case 5: printf("Сервер заданий ");
break; case 6: printf("Шлюз ");
break; case 7: printf("Сервер печати ");
break; case 8: printf("Очередь архивирования ");
break; case 9: printf("Сервер для архивирования");
break; case 0xA: printf("Очередь заданий ");
break; case 0xb: printf("Администратор ");
break; case 0x26: printf("Сервер удаленного моста ");
break; default: printf("Объект 0x%04.4X ", ObjectType);
break; } // Выводим флаг объекта, который может иметь два значения: // 0 для постоянных объектов и 1 для временных if(ObjectFlag) printf("Временный ");
else printf("Постоянный");
// Выводим байт прав, необходимых для получения доступа к объекту printf(" Доступ %02.2X", ObjectSecurity);
// Если для объекта имеются дополнительные записи, // выводим их названия if(ObjectHasProperties) Property(ObjectName, ObjectType);
} } // ================================================================= // Функция Property выводит названия дополнительных записей объектов // ================================================================= void Property(char *ObjectName, WORD ObjectType) { int ccode; BYTE PropertySecurity; long ObjectID; char SearchPropertyName[16]; char PropertyName[16]; WORD SearchObjectType; char PropertyFlag; long SequenceNumber; char PropertyHasValue; char MoreProperties; // Маска для поиска всех записей strcpy(SearchPropertyName, "*");
for(SequenceNumber=-1;;) { // Получаем запись ccode = ScanProperty(ObjectName, ObjectType, SearchPropertyName, &SequenceNumber, PropertyName, &PropertyFlag, &PropertySecurity, &PropertyHasValue, &MoreProperties);
// Если записей больше нет, завершаем цикл if(ccode) break; // Выводим название записи printf("\n\tProperty %s", PropertyName);
} }
Режимы приема сообщений
7.2. Определение режима приема сообщений
7.3. Установка режима приема сообщений
7.4. Передача сообщений пользователям
7.5. Прием сообщений
В этой главе мы рассмотрим службу передачи сообщений, которая имеется в операционной системе Novell NetWare. Эта служба позволяет организовать передачу коротких сообщений между рабочими станциями с использованием ресурсов файл-сервера. Например, утилита SEND операционной системы Novell NetWare передает сообщения именно с помощью описанных в этой главе средств. Мы расскажем вам не о всех возможностях системы передачи сообщений, а только о самой интересной, на наш взгляд, - возможности передачи сообщений от одной рабочей станции на другие и на файл-сервер. Об организации передачи сообщений через каналы (Pipes) вы можете узнать из документации, поставляющейся вместе с библиотекой функций NetWare C Interface.
Работа системы передачи сообщений основана на том, что файл-сервер для каждой подключенной к нему рабочей станции создает буфер размером 55 байт. Этот буфер используется для временного хранения сообщения, предназначенного для рабочей станции. Помимо сообщений от рабочих станций файл-сервер может передавать свои собственные сообщения, например сообщение о завершении своей работы.
Для передачи сообщения на другие рабочие станции программа должна использовать функцию SendBroadcastMessage(). Можно передать сообщение и на консоль файл-сервера, для этого используется функция BroadcastToConsole().
Режимы приема сообщенийОпределение режима приема сообщений
Установка режима приема сообщений
Передача сообщений пользователям
Программа MSGSEND
Листинг 28
Программа MSGRCV
Программа MSGRCV
7.5.1. Программа MSGRCV
Программа MSGRCV (листинг 29) изменяет текущий режим приема сообщений, блокируя автоматическую выдачу сетевой оболочкой приходящих сообщений в нижней строке экрана. Программа сама принимает эти сообщения и сама выводит их в стандартный поток вывода.
Перед завершением работы восстанавливается старый режим приема сообщений. // ===================================================
Программа MSGSEND
7.4.1. Программа MSGSEND
Программа MSGSEND (листинг 28) передает сообщение, заданное в качестве параметра, всем пользователям, подключенным к файл-серверу. Перед посылкой сообщения она с помощью функции BroadcastToConsole() выводит текстовую строку на консоль файл-сервера и записывает эту же строку в системный журнал net$log.msg, вызывая функцию LogNetworkMessage().
Для получения списка пользователей программа использует функцию GetConnectionInformation(). Эта функция возвращает информацию о пользователе, подключенном к файл-серверу, по номеру канала. Каналы нумеруются начиная с первого. Максимальное количество каналов определяется версией операционной системы, его можно определить при помощи функции GetServerInformation().
Сканируя все каналы, программа подготавливает массив номеров каналов ConnectionList[] для функции SendBroadcastMessage(), которая и выполняет рассылку сообщений. // ===================================================
Режимы приема сообщений
Что происходит, когда рабочая станция принимает сообщение? Это зависит от того, кто послал сообщение (другой пользователь или файл-сервер), а также от режима приема сообщений, установленном на рабочей станции.
Станция может принимать сообщения в четырех режимах:
0 | Этот режим используется по умолчанию и устанавливается сразу после загрузки сетевой оболочки. Когда приходит сообщение, сетевая оболочка автоматически отображает сообщение в нижней строке экрана, но только в том случае, если установлен текстовый режим работы. В графических режимах работы сообщение не отображается |
1 | В этом режиме файл-сервер запоминает в буфере приходящие от других пользователей сообщения, но сетевая оболочка отображает только сообщения, которые пришли от файл-сервера |
2 | В этом режиме файл-сервер игнорирует сообщения от других пользователей, запоминая в буфере только сообщения от файл-сервера. Автоматический вывод сообщения на экран не выполняется |
3 | Файл-сервер запоминает в буфере как сообщения, пришедшие от пользователей, так и сообщения файл-сервера. Автоматический вывод сообщения на экран не выполняется |
Для установки режима используется функция SetBroadcastMode(), текущий режим можно определить с помощью функции GetBroadcastMode().
Если сообщение не отображается автоматически, программа, запущенная на рабочей станции, может извлечь его из буфера при помощи функции GetBroadcastMessage(). Например, при работе в графическом режиме ваша программа должна уметь получать сообщения и отображать их, так как сетевая оболочка отображает сообщения только в текстовом режиме работы видеоадаптера.
Определение режима приема сообщений
Первое, что должна сделать программа, обрабатывающая сообщения, это определить текущий режим приема сообщений. Для этого она должна вызвать функцию GetBroadcastMode(): BYTE GetBroadcastMode(void);
Функция возвращает значение в диапазона от 0 до 3, соответствующее текущему режиму приема сообщений.
Вместо функции GetBroadcastMode() для определения текущего режима приема сообщений вы можете воспользоваться функцией DEh прерывания INT21h:
На входе: | AH | = | DEh; |
DL | = | В регистр DL необходимо загрузить значение 04h. | |
На выходе: | AL | = | Номер текущего режима приема сообщений (0, 1, 2, 3). |
Установка режима приема сообщений
Перед завершением работы ваша программа должна восстановить режим обработки сообщений (если задачей программы не является изменение этого режима). Для восстановления режима воспользуйтесь функцией SetBroadcastMode(): void SetBroadcastMode(BYTE BroadcastMode);
Параметр BroadcastMode определяет новый режим приема сообщений.
Вместо функции SetBroadcastMode() для определения текущего режима приема сообщений вы можете воспользоваться функцией DEh прерывания INT 21h:
На входе: | AH | = | DEh; |
DL | = | В регистр DL необходимо загрузить новое значение режима приема сообщений (0, 1, 2, 3). | |
На выходе: | Регистры не используются. |
Передача сообщений пользователям
Для передачи сообщения другим пользователям предназначена функция SendBroadcastMessage(): int SendBroadcastMessage(char *Message, WORD *ConnectionList, BYTE *ResultList, WORD ConnectionCount);
Параметр Message задает адрес текстовой строки, содержащей сообщение. Размер этой строки не должен превышать 56 байт (включая двоичный ноль, закрывающий строку).
Параметр ConnectionList - указатель на массив слов, содержащий номера каналов, используемых файл-сервером для связи с рабочими станциями. Размер этого массива определяется параметром ConnectionCount.
Параметр ResultList - массив байт, в котором для каждой станции отражается результат посылки сообщения:
0x00 | Сообщение передано успешно |
0xFC | Сообщение не передано, так как буфер сообщения для данной станции уже содержит сообщение, которое еще не было принято станцией |
0xFD | Соответствующий номер канала задан неправильно |
0xFF | Станция заблокировала прием сообщения, установив соответствующий режим приема сообщений. Этот код ошибки может появиться и в том случае, если заданный номер канала не используется |
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Ошибка ввода/вывода или нехватка памяти на сервере |
Вместо функции SendBroadcastMessage() можно использовать функцию E1h прерывания INT21h:
На входе: | AH | = | E1h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 0 BYTE ConnectionCount; // количество станций BYTE ConnectionList[ConnectionCount];// список станций BYTE MessageLength; // длина сообщения BYTE Message[MessageLength]; // сообщение };
Буфер ответа: struct REPLAY { WORD PacketLength; // размер пакета BYTE ConnectionCount; // количество станций BYTE ResultList[ConnectionCount];// результат };
Прием сообщений
Для приема сообщений предназначена функция GetBroadcastMessage(): int GetBroadcastMessage(char *MessageBuffer);
Параметр определяет адрес буфера, в который будет записано принятое сообщение. Размер буфера должен составлять не менее 56 байт.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFC | Переполнение очереди сообщений |
0xFE | Ошибка ввода/вывода или нехватка памяти на сервере |
Если в буфере нет сообщений, в первый байт буфера будет записано нулевое значение.
Вместо функции GetBroadcastMessage() можно использовать функцию E1h прерывания INT21h:
На входе: | AH | = | E1h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 1 };
Буфер ответа: struct REPLAY { WORD PacketLength; // размер пакета BYTE MessageLength; // длина сообщения BYTE Message[MessageLength]; // сообщение };
BYTE netwareVersion; BYTE netwareSubVersion; WORD
Листинг 28
Посылка сообщения станциям // Файл msgsend\msgsend.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#define WORD unsigned int #define BYTE unsigned char typedef struct { char serverName[48]; BYTE netwareVersion; BYTE netwareSubVersion; WORD maxConnectionsSupported; WORD connectionsInUse; WORD maxVolumesSupported; BYTE revisionLevel; BYTE SFTLevel; BYTE TTSLevel; WORD peakConnectionsUsed; BYTE accountingVersion; BYTE VAPversion; BYTE queingVersion; BYTE printServerVersion; BYTE virtualConsoleVersion; BYTE securityRestrictionLevel; BYTE internetBridgeSupport; } FILE_SERV_INFO; extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int BroadcastToConsole(char *);
extern "C" int SendBroadcastMessage(char*, WORD*, BYTE*, WORD);
extern "C" int LogNetworkMessage(char*);
extern "C" void GetServerInformation(int, FILE_SERV_INFO*);
extern "C" int GetConnectionInformation(WORD, char *, WORD *, long *, BYTE *);
void main(int argc, char *argv[]) { char MajorVersion=0; char MinorVersion=0; char Revision=0; long ObjectID; char ObjectName[48]; WORD ObjectType; BYTE LoginTime; FILE_SERV_INFO ServerInfo; int MaxUsers; WORD ConnectionList[250]; BYTE ResultList[250]; WORD ConnectionCount; printf("\n*MSGSEND* (C) Frolov A., 1993\n");
if(argc < 2) { printf("Введите сообщение в качестве параметра\n");
return; } // Проверяем присутствие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Выводим сообщение на консоль файл-сервера и // записываем его в журнал BroadcastToConsole("*MSGSEND* (C) Frolov A., 1993");
LogNetworkMessage("*MSGSEND* (C) Frolov A., 1993");
// Получаем информацию о сервере. Нас интересует // в первую очередь максимальное количество пользователей, // которые могут подключиться к файл-серверу GetServerInformation(sizeof(ServerInfo), &ServerInfo);
// Запоминаем максимальное количество пользователей MaxUsers = ServerInfo.maxConnectionsSupported; printf("Сервер %s, версия на %d пользователей\n", ServerInfo.serverName, MaxUsers);
// Цикл посылки сообщений. Подсчитываем количество используемых // каналов и для каждого канала заполняем массив ConnectionList[] printf("\nСообщение посылается пользователям:\n");
ConnectionCount = 0; for(int i=1, j=0; i <= MaxUsers; i++) { // Получаем информацию о канале GetConnectionInformation(i, ObjectName, &ObjectType, &ObjectID, &LoginTime);
// Если есть имя объекта, выводим его на экран if(ObjectName[0] != '\0') { printf("%s\n", ObjectName);
// Записываем номер канала в массив ConnectionList[j++] = i; ConnectionCount += 1; } } // Посылаем сообщение обнаруженным пользователям SendBroadcastMessage(argv[1], ConnectionList, ResultList, ConnectionCount);
}
Листинг 29
Прием сообщений // Файл msgrcv\msgrcv.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#define WORD unsigned int #define BYTE unsigned char extern "C" int GetNetWareShellVersion(char *,char *, char *);
extern "C" int GetBroadcastMessage(char*);
extern "C" BYTE GetBroadcastMode(void);
extern "C" void SetBroadcastMode(BYTE);
void main(void) { char MajorVersion=0; char MinorVersion=0; char Revision=0; BYTE OldBroadcastMode; char MessageBuffer[56]; int ccode; printf("\n*MSGRCV* (C) Frolov A., 1993\n");
// Проверяем присутствие сетевой оболочки asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision);
asm pop si if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n");
return; } // Сохраняем старый режим приема сообщений OldBroadcastMode = GetBroadcastMode();
// Устанавливаем режим, при котором сообщения от файл-сервера и поль- // зователей записываются в буфер, но автоматически не отображаются SetBroadcastMode(3);
// Ожидаем прихода сообщения for(;;) { // Извлекаем сообщение из буфера ccode = GetBroadcastMessage(MessageBuffer);
if(ccode) break; // Если сообщение есть в буфере, выводим его if(MessageBuffer[0] != '\0') { printf(">
>
>
%s\n", MessageBuffer);
} // Если оператор нажал на любую клавишу, // завершаем работу программы if(kbhit()) break; } // Восстанавливаем старый режим приема сообщений SetBroadcastMode(OldBroadcastMode);
}
Получение справочной информации
8.1. Получение справочной информации
8.3. Запрет и разрешение подключений к серверу
8.4. Останов файл-сервера
В последней главе нашей книги мы расскажем вам о некоторых функциях, предназначенных для получения справочной информации о состоянии файл-сервера и для управления файл-сервером.
Определение даты и времениПолучение строк описания файл-сервера
Определение имени файл-сервера
Определение возможности подключения к файл-серверу
Установка даты и времени
Функция установки даты и времени
Запрет и разрешение подключений к серверу
Останов файл-сервера
Функция установки даты и времени
8.2.1. Функция установки даты и времени
Дату и время в часах файл-сервера можно установить при помощи функции SetFileServerDateAndTime(): int SetFileServerDateAndTime(WORD Year, WORD Month, WORD Day, WORD Hour, WORD Minute, WORD Second);
Параметры этой функции задают новые значения для года (Year), месяца (Month), дня месяца (Day), часов (Hour), минут (Minute) и секунд (Second). Формат и назначение этих параметров аналогичны элементам массива, возвращаемого функцией GetFileServerDateAndTime().
В случае успешного завершения функция возвращает нулевое значение. Если эта функция вызвана пользователем, не имеющим прав оператора консоли, возвращается код ошибки C6h.
Вместо функции SetFileServerDateAndTime() можно использовать функцию E1h прерывания INT21h:
На входе: | AH | = | E1h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 202 BYTE Year; // год BYTE Month; // месяц BYTE Day; // день BYTE Hour; // часы BYTE Minute; // минуты BYTE Second; // секунды };
Буфер ответа: struct REPLAY { WORD PacketLength; // размер пакета };
Определение даты и времени
8.1.1. Определение даты и времени
Функция GetFileServerDateAndTime() заполняет массив из 7 байт информацией об установке часов на файл-сервере: void GetFileServerDateAndTime(BYTE *DateAndTime);
Параметр функции должен указывать на массив размером 7 байт. После вызова функции этот массив будет заполнен следующим образом:
Номер байта | Содержимое |
0 | Год (от 0 до 99). Значение 80 соответствует 1980 году, если значение меньше чем 80, то этот год относится к XXI веку |
1 | Месяц (от 1 до 12) |
2 | День месяца (от 1 до 31) |
3 | Час (от 0 до 23) |
4 | Минуты (от 0 до 59) |
5 | Секунды (от 0 до 59) |
6 | День недели (от 0 до 6: 0 - воскресенье, 1 - понедельник, 2 - вторник и т. д.) |
Вместо функции GetFileServerDateAndTime() вы можете воспользоваться функцией E7h прерывания INT21h:
На входе: | AH | = | E7h; |
DS:DX | = | Адрес буфера размером 7 байт для записи информации о дате и времени, установленных на файл-сервере. | |
На выходе: | Регистры не используются. |
Получение строк описания файл-сервера
8.1.2. Получение строк описания файл-сервера
Получить в текстовом виде сведения о версии операционной системы Novell NetWare и о фирме-изготовителе можно с помощью функции GetFileServerDescriptionStrings(): int GetFileServerDescriptionStrings(char *CompanyName, char *Revision, char *RevisionDate, char *CopyrightNotice);
Параметр CompanyName должен указывать на буфер размером 80 байт, в который будет записано название фирмы-изготовителя NetWare (мы полагаем, что это всегда будет Novell).
Параметр Revision должен указывать на буфер размером 80 байт, в который будут записаны номер версии и номер изменений Novell NetWare.
Параметр RevisionDate - указатель на буфер размером 24 байта, в который будет записана дата внесения изменений.
Последний параметр, CopyrightNotice, должен указывать на буфер размером 80 байт, в который будут записаны сведения о правах на копирование операционной системы Novell NetWare.
Все строки будут закрыты двоичным нулем.
При успешном завершении функция возвращает нулевое значение.
Определение имени файл-сервера
8.1.3. Определение имени файл-сервера
Для определения имени файл-сервера по номеру канала, который рабочая станция использует для связи с ним, можно при помощи функции GetFileServerName(): void GetFileServerName(WORD ConnectionID, char *FileServerName);
Для файл-сервера, заданного параметром ConnectionID (номер канала), функция возвращает имя файл-сервера в массив FileServerName размером 48 байт.
Определение возможности подключения к файл-серверу
8.1.4. Определение возможности подключения к файл-серверу
Программа, запущенная с рабочей станции пользователем с правами оператора консоли, может разрешать или запрещать подключение новых пользователей к файл-серверу. Для этого предназначены функции EnableFileServerLogin() и DisableFileServerLogin(), которые мы рассмотрим ниже. Для определения текущего состояния файл-сервера вы можете воспользоваться функцией GetFileServerLoginStatus(): int GetFileServerLoginStatus(int *LoginEnabledFlag);
По адресу, заданному параметром LoginEnabledFlag, функция запишет значение флага разрешения подключения. Если значение флага равно нулю, новые пользователи не могут подключаться к файл-серверу.
В случае успешного завершения функция возвращает нулевое значение. Если эта функция вызвана пользователем, не имеющим прав оператора консоли, возвращается код ошибки C6h.
Установка даты и времени
Иногда желательно синхронизировать часы на файл-сервере с часами рабочей станции. В этом вам поможет функция установки даты и времени в часах файл-сервера.
Запрет и разрешение подключений к серверу
Для того чтобы запретить подключение к файл-серверу новых пользователей программа должна вызывать функцию DisableFileServerLogin(): int DisableFileServerLogin(void);
В случае успешного завершения функция возвращает нулевое значение. Если эта функция вызвана пользователем, не имеющим прав оператора консоли, возвращается код ошибки C6h.
Для того чтобы вновь разрешить пользователям подключаться к файл-серверу, следует вызвать функцию EnableFileServerLogin(): int EnableFileServerLogin(void);
Эту функцию может вызывать только пользователь, имеющий права оператора консоли.
Вместо функции DisableFileServerLogin() можно использовать функцию E3h прерывания INT 21h:
На входе: | AH | = | E3h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 203 };
Буфер ответа: struct REPLAY { WORD PacketLength; // размер пакета };
Вместо функции EnableFileServerLogin() также можно использовать функцию E3h прерывания INT 21h, подготовив буфер запроса и ответа следующим образом:
Буфер запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 204 };
Буфер ответа: struct REPLAY { WORD PacketLength; // размер пакета };
Останов файл-сервера
Иногда программе может потребоваться завершить работу файл-сервера. Это можно сделать при помощи функции DownFileServer(): int DownFileServer(int ForceFlag);
Параметр ForceFlag определяет, надо ли завершать работу файл-сервера, если есть рабочие станции, открывшие файлы в сетевых каталогах.
Если параметр установлен в нуль, при попытке завершить работу файл-сервера возвращается код ошибки FFh, если имеются открытые пользователями файлы.
Если значение параметра равно единице, сервер завершает свою работу в любом случае.
В случае успешного завершения функция возвращает нулевое значение. Если эта функция вызвана пользователем, не имеющим прав оператора консоли, возвращается код ошибки C6h.
Вместо функции DownFileServer() можно использовать функцию E3h прерывания INT 21h:
На входе: | AH | = | E3h; |
DS:SI | = | Адрес буфера запроса; | |
ES:DI | = | Адрес буфера ответа. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Буфер запроса: struct REQUEST { WORD PacketLength; // размер пакета запроса BYTE Function; // должно быть равно 211 BYTE ForceFlag; // FFh - завершить в любом случае // 00h - завершить, если нет открытых // файлов };
Буфер ответа: struct REPLAY { WORD PacketLength; // размер пакета };
Разные функции
9.1. Разные функции
9.2. Каналы, подключение к файл-серверу и отключение от файл-сервера
9.3. Работа с томами файл-сервера
9.4. Отображение дисков рабочей станции на сетевые каталоги
9.5. Просмотр содержимого каталогов
9.6. Создание, переименование и удаление каталога
9.7. Работа с файлами
9.9. Работа с базой объектов Bindery
9.10. Передача и прием сообщений
9.11. Управление файл-сервером
9.12. Работа с протоколом IPX
Проверка сетевой оболочкиОбмен байтов в 16-битовом слове
Обмен байтов в 32-битовом слове
Подключение к файл-серверу
Отключение от файл-сервера
Получить номер канала первичного сервера
Получить номер канала текущего сервера
Установить предпочтительный сервер
Отключение от всех файл-серверов
Отключение от одного файл-сервера
Получить имя тома по номеру тома
Получить номер тома по имени тома
Получить информацию о томе
Создание нового элемента в таблице индексов каталога
Удаление элемента из таблицы индексов каталога
Получить индекс каталога по номеру диска
Создание временного элемента в таблице индексов каталога
Поиск подкаталогов в сетевых каталогах
Создание каталога
Переименование каталога
Удаление каталога
Получение маски прав доступа каталога
Изменение атрибутов каталога
Изменение маски доступа каталога
Поиск файлов
Изменение атрибутов файла
Получение байта расширенных атрибутов
Изменение байта расширенных атрибутов
Копирование файлов
Удаление файлов
Добавление файла в группу
Удаление файла из группы
Удаление группы и разблокирование всех файлов
Блокирование группы файлов
Разблокирование отдельных файлов
Разблокирование всех файлов
Добавление физической записи в группу
Удаление записи из группы
Разблокирование группы записей и удаление группы
Блокирование группы физических записей
Разблокирование отдельных записей
Разблокирование всех физических записей
Добавление логической записи в группу
Удаление логической записи из группы
Разблокирование всех логических записей и удаление группы
Блокирование группы логических записей
Разблокирование отдельных логических записей
Разблокирование всех логических записей
Открытие семафора
Закрытие семафора
Определение состояния семафора
Уменьшение значения семафора
Увеличение значения семафора
Определение собственного уровня доступа
Получение имени и типа объекта по его идентификатору
Получение идентификатора объекта по его имени и типу
Поиск объектов в базе Bindery
Поиск записей для объектов
Определение режима приема сообщений
Установка режима приема сообщений
Передача сообщений пользователям
Запись сообщения в журнал
Прием сообщений
Определение даты и времени
Получение строк описания файл-сервера
Определение имени файл-сервера
Получение информации о файл-сервере
Определение возможности подключения к файл-серверу
Установка даты и времени
Запрет подключения к файл-серверу
Разрешение подключения к файл-серверу
Останов файл-сервера
Работа с протоколом IPX
Инициализация драйвера IPX
Открытие сокета
Закрытие сокета
Прием IPX-пакета
Передача пакета
Отмена блока ECB
Определение собственного сетевого адреса
Типы данных
Работа с протоколом IPX
В этом разделе мы приведем описание тех функций, предназначенных для работы с протоколом IPX, которые были использованы в нашей книге. Полное описание дано в документации, поставляющейся с библиотекой Novell NetWare C Interface.
Блокирование группы файлов
Блокирование группы файлов
int LockFileSet(WORD Timeout);
Функция возвращает 0 при успешном завершении или код ошибки.
Параметры:
Timeout | период времени (в 18-х долях секунды), в течение которого файл-сервер будет ожидать, если группу файлов нельзя заблокировать немедленно |
Блокирование группы физических записей
Блокирование группы физических записей
int LockPhysicalRecordSet(BYTE LockDirective, WORD Timeout);
Функция возвращает 0 при успешном завершении или код ошибки.
Параметры:
LockDirective | параметр LockDirective задает режим блокирования. Если он равен нулю, записи блокируются для монопольного использования заблокировавшей записи программой. Если параметр имеет значение единице, записи блокируются для совместного использования в режиме чтения |
Timeout | период времени (в 18-х долях секунды), в течение которого файл-сервер будет ожидать, если группу записей нельзя заблокировать немедленно |
Блокирование группы логических записей
Блокирование группы логических записей
int LockLogicalRecordSet(WORD Timeout);
Функция возвращает 0 при успешном завершении или код ошибки.
Параметры:
Timeout | период времени (в 18-х долях секунды), в течение которого файл-сервер будет ожидать, если группу логических записей нельзя заблокировать немедленно |
Добавление файла в группу
Добавление файла в группу
int LogFile(char *FileName, BYTE LockDirective,WORD Timeout);
Функция возвращает 0 при успешном завершении или код ошибки.
Параметры:
FileName | путь к файлу, который необходимо добавить в группу |
LockDirective | параметр определяет, надо ли блокировать файл сразу после его добавления в группу |
Timeout | период времени (в 18-х долях секунды), в течение которого файл-сервер будет ожидать, если файл нельзя заблокировать немедленно |
Добавление физической записи в группу
Добавление физической записи в группу
int LogPhysicalRecord(int FileHandle, long RecordStartOffset, long RecordLength, BYTE LockDirective,WORD Timeout);
Функция возвращает 0 при успешном завершении или код ошибки.
Параметры:
FileHandle | индекс файла, которому принадлежит блокируемая запись |
RecordStartOffset | смещение от начала файла |
RecordLength | размер блокируемой записи в байтах |
LockDirective | параметр определяет, надо ли блокировать запись сразу после добавления ее в группу |
Timeout | период времени (в 18-х долях секунды), в течении которого файл-сервер будет ожидать, если запись нельзя заблокировать немедленно |