KasperskyOS Community Edition 1.1
[Topic sc_filesystems_and_net]

Состав компонента 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 и сгенерированные из них заголовочные файлы с транспортным кодом.

В начало
[Topic vfs_overview]

Создание 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

В начало
[Topic client_and_vfs_ipc_channel]

Сборка исполняемого файла 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 – в случае отдельного сетевого драйвера), а также библиотеки реализации файловых систем.

В начало
[Topic vfs_app_build]

Объединение клиента и 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 и реализацией используемой файловой системы. Кроме того, нужно добавить в решение драйвер блочного устройства.

В начало
[Topic client_and_vfs_linked]

Обзор: аргументы и переменные окружения 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-хранилище. Пример использования ROOTFS

  • VFS_CLIENT_MAX_THREADS

    Переменная окружения VFS_CLIENT_MAX_THREADS позволяет в момент запуска VFS переопределить параметр конфигурирования SDK VFS_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

В начало
[Topic vfs_args_and_envs_overview]

Монтирование файловой системы при старте

При запуске процесса VFS по умолчанию монтируется только файловая система RAMFS, в корневую директорию. Если требуется монтировать другие файловые системы, это можно сделать не только с помощью вызова mount() после запуска VFS, но и непосредственно при запуске процесса VFS – передав ему нужные аргументы и переменные окружения.

Рассмотрим три примера монтирования файловых систем при запуске VFS. Для передачи аргументов и переменных окружения процессу VFS использована программа Env.

Монтирование с помощью аргумента -l

Простой способ монтировать файловую систему – это передать процессу VFS аргумент -l <запись в формате fstab>.

В этом примере при запуске процесса с именем Vfs1 будут монтированы файловые системы devfs и romfs.

env.c

#include <env/env.h>

#include <stdlib.h>

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

#include <env/env.h>

#include <stdlib.h>

int main(int argc, char** argv)

{

const char* Vfs2Args[] = { "-f", "fstab" };

ENV_REGISTER_ARGS("Vfs2", Vfs2Args);

envServerRun();

return EXIT_SUCCESS;

}

Монтирование с помощью "внешнего" fstab

Пусть fstab-файл находится не в ROMFS-образе решения, а на диске. Чтобы использовать его для монтирования, необходимо передать VFS следующие аргументы и переменные окружения:

  1. ROOTFS. Эта переменная позволяет монтировать в корневую директорию файловую систему, в которой находится fstab-файл.
  2. UNMAP_ROMFS. Если эта переменная определена, ROMFS-хранилище удаляется. В итоге fstab-файл будет искаться на файловой системе, смонтированной с помощью переменной ROOTFS.
  3. -f. Этот аргумент используется, чтобы задать путь к fstab-файлу.

В следующем примере при запуске процесса с именем Vfs3 в корневой каталог будет монтирована файловая система ext2, на которой будет найден файл /etc/fstab для монтирования дополнительных файловых систем. ROMFS-хранилище будет удалено.

env.c

#include <env/env.h>

#include <stdlib.h>

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;

}

В начало
[Topic mount_on_start]

Разделение файловых и сетевых вызовов с помощью бэкендов 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

#include <env/env.h>

#include <stdlib.h>

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;

}

В начало
[Topic client_and_two_vfs]

Написание пользовательского бэкенда 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.

Ниже мы рассмотрим подробнее:

  1. Код бэкенда custom_client.
  2. Компоновка программы Client и бэкенда custom_client.
  3. Код программы Env.
  4. Init-описание.

Написание бэкенда custom_client

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

backend.c

#include <vfs/vfs.h>

#include <stdio.h>

#include <stdlib.h>

#include <platform/compiler.h>

#include <pthread.h>

#include <errno.h>

#include <string.h>

#include <getopt.h>

#include <assert.h>

/* Код управления файловыми дескрипторами. */

#define MAX_FDS 50

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

#include <env/env.h>

#include <stdlib.h>

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}

В начало
[Topic vfs_backends]