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