KasperskyOS Community Edition 1.1

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