Untitled
2. ПОДКЛЮЧЕНИЕ ПОЛЬЗОВАТЕЛЯ К СЕРВЕРУ
2.1. Проверка присутствия сетевой оболочки
2.2. Поиск серверов в сети
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, подключающей пользователя к файл-серверу. Если вам
потребуется создать собственную программу подключения пользователей,
вы должны выполнить все или некоторые из описанных выше действий.
Прежде чем обращаться к функциям сетевой оболочки рабочей станции,
ваша программа должна убедиться, что эта оболочка загружена. Следует
также узнать ее версию, так как состав функций сетевой оболочки
может меняться от версии к версии.
Для проверки присутствия сетевой оболочки и определения ее версии
проще всего воспользоваться функцией 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):
// ===================================================
// Листинг 1. Программа для обнаружения сетевой
// оболочки и определения ее версии
// Файл versionversion.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.%dn",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 прерывания
INT 21h.
Перед вызовом функции вам нужно соответствующим образом загрузить
регистры:
На входе: | AX | =
| EA01h. |
| ES:DI | = |
Указатель на буфер размером 40 байт, в который будет записано текстовое описание среды рабочей станции. Это описание состоит из четырех строк:
- название операционной системы;
- версия операционной системы;
- модель компьютера;
- фирма-производитель компьютера.
Последняя текстовая строка в буфере закрыта двумя двоичными нулями.
|
На выходе: | BH | =
| Верхний (major) номер версии или 0, если сетевая оболочка не загружена или ее версия меньше 2.1.
|
| BL | = |
Нижний (minor) номер версии. |
| CL | = |
Номер изменения (revision). |
Приведем вариант предыдущей программы, не использующий библиотеку
NetWare C Interface (листинг 2). Кроме версии сетевой оболочки
программа выводит содержимое буфера с текстовым описанием среды
рабочей станции.
// ================================================================
// Листинг 2. Программа для обнаружения сетевой оболочки, определе-
// ния ее версии и вывода строк описания среды рабочей станции
// Файл version1version1.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.%dn",MajorVersion,
MinorVersion, Revision);
printf("nСтроки описания среды: ");
PrintBuffer(Buffer);
}
void PrintBuffer(char *Buffer) {
char *ptr;
for(ptr = Buffer; *ptr != ' ';) {
printf("'%s' ", ptr);
ptr = ptr + strlen(ptr) + 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 прерывания
INT 21h.
Перед вызовом функции вам нужно загрузить регистры следующим образом:
На входе: | AH | =
| F1h; |
| AL | = |
0 - создать канал с файл-сервером, использовать номер канала, заданный в регистре DL;
1 - отключить пользователя и удалить канал, номер которого задан в регистре DL;
2 - отключить пользователя от файл-сервера, номер канала которого задан в регистре DL;
|
| DL | = |
Номер канала. |
На выходе: | AL | =
| Код ошибки или 0, если операция выполнена без ошибок.
|
При помощи функции F0h прерывания INT 21h вы сможете определить
первичный и текущий сервер, а также задать новый первичный или
предпочтительный сервер:
На входе: | AH | =
| F0h; |
| AL | = |
0 - установить предпочтительный файл-сервер, номер канала которого задан в регистре DL;
1 - определить текущий предпочтительный сервер, номер сервера возвращается в регистре AL;
2 - получить в регистре AL номер текущего сервера;
4 - установить первичный файл-сервер, номер канала которого задан в регистре DL;
5 - получить в регистре AL номер первичного файл-сервера;
|
| DL | = |
Номер канала. |
На выходе: | AL | =
| Код ошибки или 0, если операция выполнена без ошибок.
|
Создав канал с файл-сервером, программа еще не получила доступ
к томам сервера и другому сервису. Следующим после создания канала
этапом должно быть подключение пользователя к файл-серверу.
Для подключения пользователя к файл-серверу вы должны использовать
функцию 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" мы приведем программу, которая
умеет подключать пользователя к файл-серверу, а сейчас займемся
тем, что определим список активных файл-серверов.
2.4.1. Программа SLIST
Мы подготовили для вас программу, которая, пользуясь протоколом
SAP, определяет список активных серверов и запоминает имена серверов.
Затем для всех активных серверов программа получает дополнительную
информацию и выводит ее в стандартный поток вывода.
Программа создает объект класса SLIST. Конструктор этого объекта
получает всю необходимую информацию, которая при помощи функции
SLIST::PrintServersName(), определенной в классе SLIST, выводится
в стандартный поток (листинг 3).
// ================================================================
// Листинг 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) Фролов А.В., 1993n");
// Создаем объект класса SLIST. Конструктор этого объекта
// получает всю необходимую информацию о серверах и
// записывает ее в область данных объекта
ServerList = new SLIST(GENERAL_SERVICE);
// Если при создании объекта были ошибки, завершаем
// выполнение программы
ccode = ServerList->Error();
if(ccode) {
printf("Ошибка %dn", 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; }
};
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(). Эту функцию, а также
все, что связано с дисками сервера, мы рассмотрим в следующей
главе.
// ===================================================
// Листинг 6. Подключение к серверу
// Файл loglog.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) Фролов А.В., 1993n");
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.4Xn",
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%sn%snn%sn%sn",
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 = %dn",ccode);
}
}
else {
printf("Ошибка при подключении: %04.4Xn",ccode);
return;
}
}
|