Файловые сервисы (черновик)
В данной заметке я хотел бы поделится первым опытом написания сервисов для Plan 9, по ходу изучения материала статья будет дополнятся и коректироватся. Для написания сервиса первым делом нужно ознакомится с srv(2) где подробно описаны функции и структуры которые упрощают написание сервиса. Основу сервиса составляет структура Srv которая хранит ряд важных параметров, таких как:
Tree - хранит список файлов (и каталогов) которые предоставляются сервисом
attach - необязательная функция, проверяет аутентификационные данные, которые передаются посредством auth и разрешает подключение сервиса
auth - необязательная функция, здесь происходит аутентификация, заполняются необходимые структуры
open -
create -
read - обезательная функция
write - необязательная функция,
remove -
flush - необязательная функция,
stat -
wstat -
walk -
Все эти функции имеют одинаковый прототип:
void ххх(Req *r);
Req представляет собой запрос который должен выполнить сервис над указанным файлом (описан ниже). В процесе изучения был выработан шаблон сервиса (этот сервис может работать):
#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
static char* srvname={"mysrv"};
static char* mntpt=nil; //{"/n/mysrv"};
void myopen(Req *r);
void mywrite(Req *r);
void myread(Req *r);
void mycreate(Req *r);
Srv mysrv={
.open=myopen,
.read=myread,
.write=mywrite,
.create=mycreate,
};
void mywrite(Req *r)
{
respond(r,nil);
}
void myread(Req *r)
{
respond(r,nil);
}
void myopen(Req *r)
{
respond(r,nil);
}
void mycreate(Req *r)
{
respond(r,nil);
}
void destroyfile(File *f)
{
}
void main(int argc, char *argv[])
{
mysrv.tree=alloctree(nil,nil,DMDIR|0777,destroyfile);
createfile(mysrv.tree->root,"data",nil,0666,nil);
createfile(mysrv.tree->root,"ctl",nil,0666,nil);
postmountsrv(&mysrv,srvname,mntpt,MREPL|MCREATE);
exits(0);
}
Первые поясниния приведу на нем. Первым делом сервис заполняет структуру srv.tree, первым создается каталог в котором размещаются два файла data и ctl. Для запуска сервиса необходимо также заполнить два файловых дескриптора infd и outfd, входящих 9P сообщений и исходящих 9P соответственно, и вызвать функцию srv которая начнет их обработку, и по мере необходимости передавая управление вашим функциям open, read, write ... Для упрощения этих операций можно использовать функцию postmountsrv с таким прототипом:
void postmountsrv(Srv *s, char *name, char *mtpt, int flag)
Первый параметр упоминался выше, вторый составляет имя сервиса, mtpt - указывает на точку монтирования, flag используется если указан mtpt для вызова amount. Если не указать mtpt то сервис просто создаст файл в каталоге /srv который можно будет смонтировать посредством mount в необходимый каталог, если же mtpt указан то монтирование происходит автоматически посредством функции amount которой передается параметр flag. Вы можете проверить оба способа скомпилировав этот шаблон в двух вариантах, изменяя строку с определением mntpt на:
static char* mntpt=nil;
static char* mntpt={"/n/mysrv"};
В первом случае, запустив наш сервис вам необходимо будет выполнить следующий набор команд:
% testsrv
% cd /srv
% ls
...
mysrv
...
% mount /srv/mysrv /n/my
% cd /n/my
% lc
ctl data
%
Для удаления (остановки) сервиса его необходимо удалить из каталога /srv, а также перед этим не мешало бы размонтировать созданные каталоги:
% unmount /n/my
% rm /srv/mysrv
Каждый вызов read, write, ... должен заканчиватся функцией respond последний параметр которой, в случае ошибки содержит строку (char*) с сообщением об ошибке. Для более детального пояснения приведу полный пример сервиса:
#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
static char* srvname={"mysrv"};
static char* mntpt=nil; //{"/n/mysrv"};
void myopen(Req *r);
void mywrite(Req *r);
void myread(Req *r);
void mycreate(Req *r);
#define DATAFILE 1
#define CTLFILE 2
#define MAX_NAME 100
char Name[MAX_NAME]={"nobody"};
char *hello={"Hello "};
Srv mysrv={
.open=myopen,
.read=myread,
.write=mywrite,
.create=mycreate,
};
void mywrite(Req *r)
{
char data[1024];
Fid *fid=r->fid;
switch((ulong)fid->qid.path)
{
case CTLFILE:
memmove(data,r->ifcall.data,r->ifcall.count);
data[r->ifcall.count]='\0';
strcpy(Name,data);
break;
}
respond(r,nil);
}
void myread(Req *r)
{
char data[1024];
Fid *fid=r->fid;
switch((ulong)fid->qid.path)
{
case CTLFILE:
strcpy(data,"Name=");
strcat(data,Name);
strcat(data,"\n");
readstr(r,data);
break;
case DATAFILE:
strcpy(data,hello);
strcat(data,Name);
strcat(data,"\n");
readstr(r,data);
break;
}
respond(r,nil);
}
void myopen(Req *r)
{
respond(r,nil);
}
void mycreate(Req *r)
{
respond(r,"permission denied");
}
void destroyfile(File *f)
{
}
void main(int argc, char *argv[])
{
File *fc;
mysrv.tree=alloctree(nil,nil,DMDIR|0777,destroyfile);
fc=createfile(mysrv.tree->root,"data",nil,0666,nil);
fc->qid.path=DATAFILE;
fc=createfile(mysrv.tree->root,"ctl",nil,0666,nil);
fc->qid.path=CTLFILE;
postmountsrv(&mysrv,srvname,mntpt,MREPL|MCREATE);
exits(0);
}
И так наш сервис содержит два файла: data и ctl. Data используется для получения информации, в нашем случае - приветствия, а ctl для управления - установки имени того кого мы приветствуем. Чтение из ctl выводит текущий статус приложения - состояние переменной Name. Если мы подмонтируем сервис в каталог, например /n/my, то сеанс работы будет выглядеть так:
% cd /n/my
% cat data
Hello nobody
% cat ctl
Name=nobody
% echo -n user >ctl
% cat ctl
Name=user
% cat data
Hello user
%
Для того чтобы различать файлы, при проведении операции создании дерева каталогов мы выполнили следующее действие:
fc=createfile(mysrv.tree->root,"data",nil,0666,nil);
fc->qid.path=DATAFILE;
fc=createfile(mysrv.tree->root,"ctl",nil,0666,nil);
fc->qid.path=CTLFILE;
Qid это уникальный идентификатор назначаемый сервисом для каждого своего файла, а если уникальный то это то что нам нужно, установим его для одного файла DATAFILE , а для другого CTLFILE. Далее когда происходит вызов read или write в переменную Req *r помещаются следующие данные:
ifcall (incoming file call) - входящий запрос
ofcall (outgoing file call) - исходящий запрос
Они определены как структура Fcall и содержат такие параметры как data, offset, count, например, если пользователь записал что то в файл, и была вызвана функция write (нашего сервиса) то параметр offset указывает позицию куда была произведена запись, count - сколько байт записано, а data представляет эти данные, программе необходимо записать эти данные по назначению, и заполнить структуру ofcall в соответствии с выполненой операцией, например, сколько байт записано... Для упрощения чтения из файла существует функция readstr и readbuf которые записывают строку (или количество байт) в предоставляемый сервисом файл, для этого надо записать данные в r->ofcall.data (и возможно перераспределить память), установить счетчик count, "передвинуть" offset, все это выполняет readstr и readbuf.
На этом текущая редакция документа заканчивается, дополнения будут позже. Во-первых не реализована функция destroyfile, что чревато утечкой памяти... Во-вторых необходим более удобный механизм идентификации файлов внутри сервиса, почти каждая из рассмотреных структур (и что самое важное в File) содержит в себе переменную aux в которой программист может сохранять любые данные... В-третих необходимо более детально описать использованные структуры... Если у вас есть дополнительные пожелания то отправляйте их на мой почтовый ящик mailto:rs-rlab@narod.ru