Содержание
- Файловые системы и сеть
- Состав компонента VFS
- Создание IPC-канала до VFS
- Сборка исполняемого файла VFS
- Объединение клиента и VFS в один исполняемый файл
- Обзор: аргументы и переменные окружения VFS
- Монтирование файловой системы при старте
- Разделение файловых и сетевых вызовов с помощью бэкендов VFS
- Написание пользовательского бэкенда VFS
Состав компонента VFS
Компонент VFS содержит набор исполняемых файлов, библиотек и файлов описаний, позволяющих использовать файловые системы и/или сетевой стек, вынесенные в отдельный процесс VFS (Virtual File System – виртуальная файловая система). При необходимости можно собрать собственные реализации VFS.
Библиотеки VFS
CMake-пакет vfs
содержит следующие библиотеки:
vfs_fs
– содержит реализации defvs, ramfs и romfs, а также позволяет добавить в VFS реализации других файловых систем;vfs_net
– содержит реализацию defvs и сетевого стека;vfs_imp
– содержит в себе сумму компонентовvfs_fs
иvfs_net
;vfs_remote
– клиентская транспортная библиотека; преобразует локальные вызовы в IPC-запросы к VFS и принимает IPC-ответы;vfs_server
– серверная транспортная библиотека VFS; принимает IPC-запросы, преобразует их в локальные вызовы и отправляет IPC-ответы;vfs_local
– используется для статической компоновки клиента с библиотеками VFS.
Исполняемые файлы VFS
CMake-пакет precompiled_vfs
содержит следующие исполняемые файлы:
VfsRamFs
VfsSdCardFs
VfsNet
Исполняемые файлы VfsRamFs
и VfsSdCardFs
включают в себя библиотеки vfs_server
, vfs_fs
, vfat
и lwext4
. Исполняемый файл VfsNet
включает в себя библиотеки vfs_server
, vfs_imp
и dnet_imp
.
Каждый из этих исполняемых файлов имеет собственные значения аргументов и переменных окружения по умолчанию.
При необходимости можно самостоятельно собрать исполняемый файл VFS с нужной функциональностью.
Файлы описаний VFS
В директории /opt/KasperskyOS-Community-Edition-<version>/sysroot-aarch64-kos/include/kl/
находятся следующие файлы VFS:
VfsRamFs.edl
,VfsSdCardFs.edl
,VfsNet.edl
иVfsEntity.edl
и сгенерированные из них заголовочные файлы с транспортным кодом;Vfs.cdl
и сгенерированныйVfs.cdl.h
;Vfs*.idl
и сгенерированные из них заголовочные файлы с транспортным кодом.
Создание IPC-канала до VFS
Рассмотрим программу Client
, использующую файловые системы и сокеты Беркли. Для обработки ее вызовов запустим один процесс VFS (с именем VfsFsnet
). В этот процесс будут направляться как "сетевые", так и "файловые" вызовы. Такой подход используется в тех случаях, когда не требуется разделение "файловых" и "сетевых" информационных потоков.
Чтобы взаимодействие процессов Client
и VfsFsnet
было корректным, имя IPC-канала между ними должно задаваться макросом _VFS_CONNECTION_ID
, объявленным в файле vfs/defs.h
.
Ниже приводится фрагмент init‑описания для соединения процессов Client
и VfsFsnet
.
init.yaml
- name: Client
connections:
- target: VfsFsnet
id: {var: _VFS_CONNECTION_ID, include: vfs/defs.h}
- name: VfsFsnet
Сборка исполняемого файла VFS
При сборке исполняемого файла VFS можно включить в него именно ту функциональность, которая требуется, например:
- реализацию той или иной файловый системы;
- сетевой стек;
- сетевой драйвер.
Например, при разделении файловых и сетевых вызовов понадобится сборка "файловой версии" и "сетевой версии" VFS. В некоторых случаях в VFS потребуется включить и сетевой стек, и файловые системы ("полная версия" VFS).
Сборка "файловой версии" VFS
Рассмотрим программу VFS, содержащую только реализацию файловой системы lwext4 и не содержащую сетевого стека. Для сборки такого исполняемого файла необходимо файл с функцией main()
скомпоновать с библиотеками vfs_server
, vfs_fs
и lwext4
:
CMakeLists.txt
project (vfsfs)
include (platform/nk)
# Установка флагов компиляции
project_header_default ("STANDARD_GNU_11:YES" "STRICT_WARNINGS:NO")
add_executable (VfsFs "src/vfs.c")
# Компоновка с библиотеками VFS
target_link_libraries (VfsFs
${vfs_SERVER_LIB}
${LWEXT4_LIB}
${vfs_FS_LIB})
# Подготовка VFS для соединения с процессом ramdisk-драйвера
set_target_properties (VfsFs PROPERTIES ${blkdev_ENTITY}_REPLACEMENT ${ramdisk_ENTITY})
Драйвер блочного устройства не может быть скомпонован с VFS, поэтому всегда должен быть запущен как отдельный процесс.
Взаимодействие трех процессов: клиента, "файловой версии" VFS и драйвера блочного устройства
Сборка "сетевой версии" VFS совместно с сетевым драйвером
Рассмотрим программу VFS, содержащую сетевой стек с драйвером и не содержащую реализаций файловых систем. Для сборки такого исполняемого файла необходимо файл с функцией main()
скомпоновать с библиотеками vfs_server
, vfs_implementation
и dnet_implementation
.
CMakeLists.txt
project (vfsnet)
include (platform/nk)
# Установка флагов компиляции
project_header_default ("STANDARD_GNU_11:YES" "STRICT_WARNINGS:NO")
add_executable (VfsNet "src/vfs.c")
# Компоновка с библиотеками VFS
target_link_libraries (VfsNet
${vfs_SERVER_LIB}
${vfs_IMPLEMENTATION_LIB}
${dnet_IMPLEMENTATION_LIB})
# Отключение драйвера блочного устройства
set_target_properties (VfsNet PROPERTIES ${blkdev_ENTITY}_REPLACEMENT "")
Библиотека dnet_implementation
уже включает в себя сетевой драйвер, поэтому запуск отдельного процесса драйвера не требуется.
Взаимодействие процесса Client с процессом "сетевой версии" VFS
Сборка "сетевой версии" VFS с отдельным сетевым драйвером
Еще один вариант сборки "сетевой версии" VFS – без сетевого драйвера. Сам сетевой драйвер необходимо будет запустить как отдельный процесс. Взаимодействие с драйвером происходит через IPC с помощью библиотеки dnet_client
.
Таким образом, необходимо файл с функцией main()
скомпоновать с библиотеками vfs_server
, vfs_implementation
и dnet_client
.
CMakeLists.txt
project (vfsnet)
include (platform/nk)
# Установка флагов компиляции
project_header_default ("STANDARD_GNU_11:YES" "STRICT_WARNINGS:NO")
add_executable (VfsNet "src/vfs.c")
# Компоновка с библиотеками VFS
target_link_libraries (VfsNet
${vfs_SERVER_LIB}
${vfs_IMPLEMENTATION_LIB}
${dnet_CLIENT_LIB})
# Отключение драйвера блочного устройства
set_target_properties (VfsNet PROPERTIES ${blkdev_ENTITY}_REPLACEMENT "")
Взаимодействие трех процессов: клиента, "сетевой версии" VFS и сетевого драйвера
Сборка "полной версии" VFS
Если в VFS требуется включить как сетевой стек, так и реализации файловых систем, то при сборке следует использовать библиотеки vfs_server
, vfs_implementation
, dnet_implementation
(или dnet_client
– в случае отдельного сетевого драйвера), а также библиотеки реализации файловых систем.
Объединение клиента и VFS в один исполняемый файл
Рассмотрим программу Client
, использующую сокеты Беркли. Вызовы, которые выполняет Client
, должны направляться в VFS. Обычный путь состоит в запуске отдельного процесса VFS и создании IPC-канала. Однако вместо этого можно интегрировать функциональность VFS в сам исполняемый файл Client
. Для этого нужно при сборке исполняемого файла Client
скомпоновать его с библиотекой vfs_local
, которая будет принимать вызовы, а также с библиотеками реализации – vfs_implementation
и dnet_implementation
.
Локальную компоновку с VFS удобно использовать при отладке. Кроме того, вызовы для работы с сетью могут обрабатываться намного быстрее за счет исключения IPC-вызовов. Тем не менее, изоляция VFS в отдельном процессе и IPC-взаимодействие с ним рекомендуется во всех случаях как более безопасный подход.
Ниже приведен сборочный скрипт исполняемого файла Client
.
CMakeLists.txt
project (client)
include (platform/nk)
# Установка флагов компиляции
project_header_default ("STANDARD_GNU_11:YES" "STRICT_WARNINGS:NO")
# Генерация файла Client.edl.h
nk_build_edl_files (client_edl_files NK_MODULE "client" EDL "${CMAKE_SOURCE_DIR}/resources/edl/Client.edl")
add_executable (Client "src/client.c")
add_dependencies (Client client_edl_files)
# Компоновка с библиотеками VFS
target_link_libraries (Client ${vfs_LOCAL_LIB} ${vfs_IMPLEMENTATION_LIB} ${dnet_IMPLEMENTATION_LIB}
Если Client
использует файловые системы, то помимо vfs_local
, его нужно скомпоновать с библиотекой vfs_fs
и реализацией используемой файловой системы. Кроме того, нужно добавить в решение драйвер блочного устройства.
Обзор: аргументы и переменные окружения VFS
Аргументы VFS
-l <запись в формате fstab>
Аргумент
-l
позволяет монтировать файловую систему.-f <путь к файлу fstab>
Аргумент
-f
позволяет передать файл с записями в формате fstab для монтирования файловых систем. Файл будет искаться в ROMFS-хранилище. Если переменнаяUMNAP_ROMFS
определена, то файл будет искаться на файловой системе, смонтированной с помощью переменнойROOTFS
.
Пример использования аргументов -l и -f
Переменные окружения VFS
UNMAP_ROMFS
Если переменная
UNMAP_ROMFS
определена, то ROMFS-хранилище будет удалено. Это позволяет сэкономить память и изменить поведение при использовании аргумента-f
.ROOTFS = <запись в формате fstab>
Переменная
ROOTFS
позволяет монтировать файловую систему в корневой каталог. В комбинации с переменнойUNMAP_ROMFS
и аргументом-f
позволяет искать fstab-файл на монтированной файловой системе, а не в ROMFS-хранилище. Пример использования ROOTFSVFS_CLIENT_MAX_THREADS
Переменная окружения
VFS_CLIENT_MAX_THREADS
позволяет в момент запуска VFS переопределить параметр конфигурирования SDKVFS_CLIENT_MAX_THREADS
.-
_VFS_NETWORK_BACKEND=<имя бэкенда>:<имя IPC-канала до VFS>
Переменная _VFS_NETWORK_BACKEND
задает используемый для "сетевых" вызовов бэкенд. Можно указать имя стандартного бэкенда: client, server или local, а также имя пользовательского бэкенда. Если используется бэкенд local, то имя IPC-канала не указывается (_VFS_NETWORK_BACKEND=local:
). Может быть указано два и больше IPC-канала через запятую.
_VFS_FILESYSTEM_BACKEND=<имя бэкенда>:<имя IPC-канала до VFS>
Переменная
_VFS_FILESYSTEM_BACKEND
задает используемый для "файловых" вызовов бэкенд. Имя бэкенда и имя IPC-канала до VFS задаются так же, как и для переменной_VFS_NETWORK_BACKEND
.
Значения по умолчанию
Для исполняемого файла VfsRamFs
:
ROOTFS = ramdisk0,0 / ext4 0
VFS_FILESYSTEM_BACKEND = server:kl.VfsRamFs
Для исполняемого файла VfsSdCardFs
:
ROOTFS = mmc0,0 / fat32 0
VFS_FILESYSTEM_BACKEND = server:kl.VfsSdCardFs
-l nodev /tmp ramfs 0
-l nodev /var ramfs 0
Для исполняемого файла VfsNet
:
VFS_NETWORK_BACKEND = server:kl.VfsNet
VFS_FILESYSTEM_BACKEND = server:kl.VfsNet
-l devfs /dev devfs 0
Монтирование файловой системы при старте
При запуске процесса VFS по умолчанию монтируется только файловая система RAMFS, в корневую директорию. Если требуется монтировать другие файловые системы, это можно сделать не только с помощью вызова mount()
после запуска VFS, но и непосредственно при запуске процесса VFS – передав ему нужные аргументы и переменные окружения.
Рассмотрим три примера монтирования файловых систем при запуске VFS. Для передачи аргументов и переменных окружения процессу VFS использована программа Env
.
Монтирование с помощью аргумента -l
Простой способ монтировать файловую систему – это передать процессу VFS аргумент -l <запись в формате fstab>
.
В этом примере при запуске процесса с именем Vfs1
будут монтированы файловые системы devfs и romfs.
env.c
int main(int argc, char** argv)
{
const char* Vfs1Args[] = {
"-l", "devfs /dev devfs 0",
"-l", "romfs /etc romfs 0"
};
ENV_REGISTER_ARGS("Vfs1", Vfs1Args);
envServerRun();
return EXIT_SUCCESS;
}
Монтирование с помощью fstab из ROMFS
Если при сборке решения добавить fstab-файл, после старта он будет доступен через ROMFS-хранилище. Его можно использовать для монтирования, передав процессу VFS аргумент -f <путь к fstab-файлу>
.
В этом примере при запуске процесса с именем Vfs2
будут монтированы файловые системы, заданные через файл fstab
, который был добавлен при сборке решения.
env.c
int main(int argc, char** argv)
{
const char* Vfs2Args[] = { "-f", "fstab" };
ENV_REGISTER_ARGS("Vfs2", Vfs2Args);
envServerRun();
return EXIT_SUCCESS;
}
Монтирование с помощью "внешнего" fstab
Пусть fstab-файл находится не в ROMFS-образе решения, а на диске. Чтобы использовать его для монтирования, необходимо передать VFS следующие аргументы и переменные окружения:
ROOTFS
. Эта переменная позволяет монтировать в корневую директорию файловую систему, в которой находится fstab-файл.UNMAP_ROMFS
. Если эта переменная определена, ROMFS-хранилище удаляется. В итоге fstab-файл будет искаться на файловой системе, смонтированной с помощью переменнойROOTFS
.-f
. Этот аргумент используется, чтобы задать путь к fstab-файлу.
В следующем примере при запуске процесса с именем Vfs3
в корневой каталог будет монтирована файловая система ext2, на которой будет найден файл /etc/fstab
для монтирования дополнительных файловых систем. ROMFS-хранилище будет удалено.
env.c
int main(int argc, char** argv)
{
const char* Vfs3Args[] = { "-f", "/etc/fstab" };
const char* Vfs3Envs[] = {
"ROOTFS=ramdisk0,0 / ext2 0",
"UNMAP_ROMFS=1"
};
ENV_REGISTER_PROGRAM_ENVIRONMENT("Vfs3", Vfs3Args, Vfs3Envs);
envServerRun();
return EXIT_SUCCESS;
}
Разделение файловых и сетевых вызовов с помощью бэкендов VFS
В этом примере: паттерн безопасной разработки, предусматривающий разделение "сетевых" и "файловых" информационных потоков.
Рассмотрим программу Client
, использующую файловые системы и сокеты Беркли. Для обработки ее вызовов мы запустим не один, а два отдельных VFS-процесса из исполняемых файлов VfsFirst
и VfsSecond
. Через переменные окружения мы зададим файловые бэкенды как работающие через канал до VfsFirst
, а сетевые бэкенды – как работающие через канал до VfsSecond
. Будем использовать стандартные бэкенды client и server. Благодаря этому мы перенаправим "файловые вызовы" Client
в VfsFirst
, а "сетевые" – в VfsSecond
. Чтобы передать процессам переменные окружения, добавим в решение программу Env
.
Init-описание решения представлено ниже. Процесс Client
будет соединен с процессами VfsFirst
и VfsSecond
, при этом каждый из трех процессов соединен с процессом Env
. Обратите внимание, что имя IPC-канала до процесса Env
задается с помощью переменной ENV_SERVICE_NAME
.
init.yaml
entities:
- name: Env
- name: Client
connections:
- target: Env
id: {var: ENV_SERVICE_NAME, include: env/env.h}
- target: VfsFirst
id: VFS1
- target: VfsSecond
id: VFS2
- name: VfsFirst
connections:
- target: Env
id: {var: ENV_SERVICE_NAME, include: env/env.h}
- name: VfsSecond
connections:
- target: Env
id: {var: ENV_SERVICE_NAME, include: env/env.h}
Чтобы направить все "файловые" вызовы в VfsFirst
, зададим значение переменной окружения _VFS_FILESYSTEM_BACKEND
следующим образом:
- для
VfsFirst
:_VFS_FILESYSTEM_BACKEND=server:<имя IPC-канала до VfsFirst>
; - для
Client
:_VFS_FILESYSTEM_BACKEND=client:<имя IPC-канала до VfsFirst>
.
Для направления "сетевых" вызовов в VfsSecond
используем аналогичную переменную окружения _VFS_NETWORK_BACKEND
:
- для
VfsSecond
зададим:_VFS_NETWORK_BACKEND=server:<имя IPC-канала до VfsSecond>
; - для
Client
:_VFS_NETWORK_BACKEND=client:<имя IPC-канала до VfsSecond>
.
Значение переменных окружения зададим через программу Env
, код которой представлен ниже.
env.c
int main(void)
{
const char* vfs_first_envs[] = { "_VFS_FILESYSTEM_BACKEND=server:VFS1" };
ENV_REGISTER_VARS("VfsFirst", vfs_first_envs);
const char* vfs_second_envs[] = { "_VFS_NETWORK_BACKEND=server:VFS2" };
ENV_REGISTER_VARS("VfsSecond", vfs_second_envs);
const char* client_envs[] = { "_VFS_FILESYSTEM_BACKEND=client:VFS1", "_VFS_NETWORK_BACKEND=client:VFS2" };
ENV_REGISTER_VARS("Client", client_envs);
envServerRun();
return EXIT_SUCCESS;
}
Написание пользовательского бэкенда VFS
В этом примере: изменение логики обработки файловых вызовов с помощью специального бэкенда VFS.
Рассмотрим решение, включающее процессы Client
, VfsFirst
и VfsSecond
. Пусть процесс Client
соединен с VfsFirst
и VfsSecond
с помощью IPC-каналов.
Задача: сделать так, чтобы обращения процесса Client
к файловой системе fat32 обрабатывались процессом VfsFirst
, а обращения к ext4 обрабатывались VfsSecond
. Для решения этой задачи можно использовать механизм бэкендов VFS, причем даже не придется менять код программы Client
.
Мы напишем пользовательский бэкенд custom_client
, который будет отправлять вызовы по каналу VFS1
или VFS2
, в зависимости от того, начинается ли путь к файлу с /mnt1. Для отправки вызовов custom_client
будет использовать стандартные бэкенды client
, то есть будет являться проксирующим бэкендом.
С помощью аргумента -l мы монтируем fat32 в директорию /mnt1 для процесса VfsFirst
и ext4 в /mnt2 для процесса VfsSecond
. (Предполагается, что VfsFirst
содержит реализацию fat32, а VfsSecond
– реализацию ext4.) С помощью переменной окружения _VFS_FILESYSTEM_BACKEND
зададим используемые процессами бэкенды (custom_client и server) и IPC-каналы (VFS1 и VFS2).
Наконец, с помощью init-описания зададим имена IPC-каналов: VFS1 и VFS2.
Ниже мы рассмотрим подробнее:
- Код бэкенда
custom_client
. - Компоновка программы
Client
и бэкендаcustom_client
. - Код программы
Env
. - Init-описание.
Написание бэкенда custom_client
Этот файл содержит реализацию проксирующего пользовательского бэкенда, передающего вызовы в один из двух стандартных бэкендов client. Логика выбора бэкенда зависит от используемого пути или от дескриптора файла и управляется дополнительными структурами данных.
backend.c
/* Код управления файловыми дескрипторами. */
struct entry
{
Handle handle;
bool is_vfat;
};
struct fd_array
{
struct entry entries[MAX_FDS];
int pos;
pthread_rwlock_t lock;
};
struct fd_array fds = { .pos = 0, .lock = PTHREAD_RWLOCK_INITIALIZER };
int insert_entry(Handle fd, bool is_vfat)
{
pthread_rwlock_wrlock(&fds.lock);
if (fds.pos == MAX_FDS)
{
pthread_rwlock_unlock(&fds.lock);
return -1;
}
fds.entries[fds.pos].handle = fd;
fds.entries[fds.pos].is_vfat = is_vfat;
fds.pos++;
pthread_rwlock_unlock(&fds.lock);
return 0;
}
struct entry *find_entry(Handle fd)
{
pthread_rwlock_rdlock(&fds.lock);
for (int i = 0; i < fds.pos; i++)
{
if (fds.entries[i].handle == fd)
{
pthread_rwlock_unlock(&fds.lock);
return &fds.entries[i];
}
}
pthread_rwlock_unlock(&fds.lock);
return NULL;
}
/* Структура пользовательского бэкенда. */
struct context
{
struct vfs wrapper;
pthread_rwlock_t lock;
struct vfs *vfs_vfat;
struct vfs *vfs_ext4;
};
struct context ctx =
{
.wrapper =
{
.dtor = _vfs_backend_dtor,
.disconnect_all_clients = _disconnect_all_clients,
.getstdin = _getstdin,
.getstdout = _getstdout,
.getstderr = _getstderr,
.open = _open,
.read = _read,
.write = _write,
.close = _close,
}
};
/* Реализация методов пользовательского бэкенда. */
static bool is_vfs_vfat_path(const char *path)
{
char vfat_path[5] = "/mnt1";
if (memcmp(vfat_path, path, sizeof(vfat_path)) != 0)
return false;
return true;
}
static void _vfs_backend_dtor(struct vfs *vfs)
{
ctx.vfs_vfat->dtor(ctx.vfs_vfat);
ctx.vfs_ext4->dtor(ctx.vfs_ext4);
}
static void _disconnect_all_clients(struct vfs *self, int *error)
{
(void)self;
(void)error;
ctx.vfs_vfat->disconnect_all_clients(ctx.vfs_vfat, error);
ctx.vfs_ext4->disconnect_all_clients(ctx.vfs_ext4, error);
}
static Handle _getstdin(struct vfs *self, int *error)
{
(void)self;
Handle handle = ctx.vfs_vfat->getstdin(ctx.vfs_vfat, error);
if (handle != INVALID_HANDLE)
{
if (insert_entry(handle, true))
{
*error = ENOMEM;
return INVALID_HANDLE;
}
}
return handle;
}
static Handle _getstdout(struct vfs *self, int *error)
{
(void)self;
Handle handle = ctx.vfs_vfat->getstdout(ctx.vfs_vfat, error);
if (handle != INVALID_HANDLE)
{
if (insert_entry(handle, true))
{
*error = ENOMEM;
return INVALID_HANDLE;
}
}
return handle;
}
static Handle _getstderr(struct vfs *self, int *error)
{
(void)self;
Handle handle = ctx.vfs_vfat->getstderr(ctx.vfs_vfat, error);
if (handle != INVALID_HANDLE)
{
if (insert_entry(handle, true))
{
*error = ENOMEM;
return INVALID_HANDLE;
}
}
return handle;
}
static Handle _open(struct vfs *self, const char *path, int oflag, mode_t mode, int *error)
{
(void)self;
Handle handle;
bool is_vfat = false;
if (is_vfs_vfat_path(path))
{
handle = ctx.vfs_vfat->open(ctx.vfs_vfat, path, oflag, mode, error);
is_vfat = true;
}
else
handle = ctx.vfs_ext4->open(ctx.vfs_ext4, path, oflag, mode, error);
if (handle == INVALID_HANDLE)
return INVALID_HANDLE;
if (insert_entry(handle, is_vfat))
{
if (is_vfat)
ctx.vfs_vfat->close(ctx.vfs_vfat, handle, error);
*error = ENOMEM;
return INVALID_HANDLE;
}
return handle;
}
static ssize_t _read(struct vfs *self, Handle fd, void *buf, size_t count, bool *nodata, int *error)
{
(void)self;
struct entry *found_entry = find_entry(fd);
if (found_entry != NULL && found_entry->is_vfat)
return ctx.vfs_vfat->read(ctx.vfs_vfat, fd, buf, count, nodata, error);
return ctx.vfs_ext4->read(ctx.vfs_ext4, fd, buf, count, nodata, error);
}
static ssize_t _write(struct vfs *self, Handle fd, const void *buf, size_t count, int *error)
{
(void)self;
struct entry *found_entry = find_entry(fd);
if (found_entry != NULL && found_entry->is_vfat)
return ctx.vfs_vfat->write(ctx.vfs_vfat, fd, buf, count, error);
return ctx.vfs_ext4->write(ctx.vfs_ext4, fd, buf, count, error);
}
static int _close(struct vfs *self, Handle fd, int *error)
{
(void)self;
struct entry *found_entry = find_entry(fd);
if (found_entry != NULL && found_entry->is_vfat)
return ctx.vfs_vfat->close(ctx.vfs_vfat, fd, error);
return ctx.vfs_ext4->close(ctx.vfs_ext4, fd, error);
}
/* Конструктор пользовательского бэкенда. ctx.vfs_vfat и ctx.vfs_ext4 инициализируются
* как стандартные бэкенды с именем "client". */
static struct vfs *_vfs_backend_create(Handle client_id, const char *config, int *error)
{
(void)config;
ctx.vfs_vfat = _vfs_init("client", client_id, "VFS1", error);
assert(ctx.vfs_vfat != NULL && "Can't initilize client backend!");
assert(ctx.vfs_vfat->dtor != NULL && "VFS FS backend has not set the destructor!");
ctx.vfs_ext4 = _vfs_init("client", client_id, "VFS2", error);
assert(ctx.vfs_ext4 != NULL && "Can't initilize client backend!");
assert(ctx.vfs_ext4->dtor != NULL && "VFS FS backend has not set the destructor!");
return &ctx.wrapper;
}
/* Регистрация пользовательского бэкенда под именем custom_client. */
static void _vfs_backend(create_vfs_backend_t *ctor, const char **name)
{
*ctor = &_vfs_backend_create;
*name = "custom_client";
}
REGISTER_VFS_BACKEND(_vfs_backend)
Компоновка программы Client и бэкенда custom_client
Написанный бэкенд скомпилируем в библиотеку:
CMakeLists.txt
add_library (backend_client STATIC "src/backend.c")
Готовую библиотеку backend_client
скомпонуем с программой Client
:
CMakeLists.txt (фрагмент)
add_dependencies (Client vfs_backend_client backend_client)
target_link_libraries (Client
pthread
${vfs_CLIENT_LIB}
"-Wl,--whole-archive" backend_client "-Wl,--no-whole-archive" backend_client
)
Написание программы Env
Чтобы передать процессам аргументы и переменные окружения, используем программу Env
.
env.c
int main(int argc, char** argv)
{
/* Монтирование fat32 в /mnt1 для процесса VfsFirst и ext4 в /mnt2 для процесса VfsSecond. */
const char* VfsFirstArgs[] = {
"-l", "ahci0 /mnt1 fat32 0"
};
ENV_REGISTER_ARGS("VfsFirst", VfsFirstArgs);
const char* VfsSecondArgs[] = {
"-l", "ahci1 /mnt2 ext4 0"
};
ENV_REGISTER_ARGS("VfsSecond", VfsSecondArgs);
/* Задание файловых бэкендов. */
const char* vfs_first_args[] = { "_VFS_FILESYSTEM_BACKEND=server:VFS1" };
ENV_REGISTER_VARS("VfsFirst", vfs_first_args);
const char* vfs_second_args[] = { "_VFS_FILESYSTEM_BACKEND=server:VFS2" };
ENV_REGISTER_VARS("VfsSecond", vfs_second_args);
const char* client_fs_envs[] = { "_VFS_FILESYSTEM_BACKEND=custom_client:VFS1,VFS2" };
ENV_REGISTER_VARS("Client", client_fs_envs);
envServerRun();
return EXIT_SUCCESS;
}
Изменение init.yaml
Для IPC-каналов, соединяющих процесс Client
с процессами VfsFirst
и VfsSecond
, необходимо задать те же имена, которые мы указали в переменной окружения _VFS_FILESYSTEM_BACKEND
: VFS1 и VFS2.
init.yaml
entities:
- name: vfs_backend.Env
- name: vfs_backend.Client
connections:
- target: vfs_backend.Env
id: {var: ENV_SERVICE_NAME, include: env/env.h}
- target: vfs_backend.VfsFirst
id: VFS1
- target: vfs_backend.VfsSecond
id: VFS2
- name: vfs_backend.VfsFirst
connections:
- target: vfs_backend.Env
id: {var: ENV_SERVICE_NAME, include: env/env.h}
- name: vfs_backend.VfsSecond
connections:
- target: vfs_backend.Env
id: {var: ENV_SERVICE_NAME, include: env/env.h}