Динамическое расширение ядра Linux — добавляем функцию «удалить в корзину»

в 19:02, , рубрики: hooking, linux, linux kernel, системное программирование, метки: , ,

Многим пользователям Linux, особенно тем, кто по тем или иным причинам перешёл на эту ОС с Windows, не хватает возможности удаления файлов «в корзину». Кроме того, наверняка, каждый, кто пользовался Linux'ом испытывал и по ошибке удалял какой-либо файл, испытывал смешаные чувства от отсутствия простой возможности восстановить утраченные данные.

В продолжение предшествующего материала, посвящённого перехвату функций ядра Linux, представляю способ использования разработанного ранее фреймворка для создания модуля ядра Linux, реализующего возможность удаления файлов «в корзину» (just for fun).

Постановка задачи

Будем стремиться создать возможность удаления файлов таким образом, чтобы они попадали в «корзину». При этом, в роли корзины будет выступать специальный каталог, находящийся в корне файловой системы (.fstrash). Суть перемещения в корзину будет сводиться к тому, чтобы в момент удаления файла создавать для него «жёсткую ссылку» в этом каталоге. В этом случае, операция удаления, следующая за операцией создания жёсткой ссылки, удалит лишь «старое имя» файла, но не его содержимое.

Итак, для того, чтобы иметь возможность удаления в корзину, необхдимо:

  • использовать ФС с поддержкой жёсткких ссылок (например, ext4)
  • перехватить функцию драйвера соответствующей ФС, реализующую удаление (например, ext4_unlink)
  • в момент удаления дать файлу «второе имя», создав для него жёсткую ссылку

Реализация

Начнём с того, что рассмотрим функцию fstrash_unlink, реализующую логику работы с корзиной:

static struct qstr fstrash = {
        .len = 8, .name = ".fstrash",
};

static int fstrash_unlink(struct inode * inode, struct dentry * dentry)
{
        int result = -EINVAL;

        /* handle real deletes only */
        if (dentry->d_inode->i_nlink == 1) {
                struct dentry * trash = NULL;

                trash = d_lookup(inode->i_sb->s_root, &fstrash);
                if (trash) {
                        /* don't loop while deleting from the trash itself */
                        if (trash->d_inode && (trash->d_inode != inode))
                                result = move_to_trash(trash, dentry);
                        dput(trash);
                }
        }

        return result;
}

В первую очередь, данная функция проверяет счётчик ссылок (i_nlink) на элементе dentry, описывающем файл. Сравнение с единицей необходимо для того, чтобы убедиться в том, что запрашиваемая операция (удаление) ведёт именно к удалению файла, т.к. счётчик i_nlink содержит количество элементов каталогов, ссылающихся на данный файл (число жёстких ссылок). Очевидно, что значение «1» соответствует единственной (последней) ссылке.

Далее, осуществляется поиск «корзины» и, в случае, если она обнаружена и запрашиваемая операция удаления не является операцией удаления из самой корзины (проверка trash->d_inode != inode), вызывается функция создания жёсткой ссылки (перемещения файла в корзину) — move_to_trash:

static int move_to_trash(struct dentry * trash, struct dentry * object)
{
        int result;
        char name[64];
        struct dentry * de;

        snprintf(name, sizeof(name), "XXX-%lu-%s", 
                object->d_inode->i_ino, object->d_name.name);

        de = d_alloc_name(trash, name);
        if (!de)
                return -ENOMEM;

        trash->d_inode->i_op->lookup(trash->d_inode, de, 0);

        inc_nlink(object->d_inode);

        mutex_lock(&trash->d_inode->i_mutex);
        result = trash->d_inode->i_op->link(object, trash->d_inode, de);
        mutex_unlock(&trash->d_inode->i_mutex);

        drop_nlink(object->d_inode);
        if (!result)
                mark_inode_dirty(object->d_inode);

        dput(de);

        return result;
}

Как видно, в этой функции происходит:

  • формирование имени файла по шаблону (XXX-<номер_инода>-<имя_файла>)
  • создание нового имени файла посредством операции i_op->lookup для нового имени файла
  • создание жёсткой ссылки — операция i_op->link

В случае успешного выполнения перечисленных операций, объкт отмечается как «грязный» при помощи функции mark_inode_dirty, что сигнализирует системе о необходимости синхронизации метаданных этого файла.

Таким образом, в соответствии с рассматриваемым ранее способом встраивания в ядро, перехват ключевой функции удаления для ФС ext4 ext4_unlink будет выглядеть следующим образом:

DECLARE_KHOOK(ext4_unlink);
int khook_ext4_unlink(struct inode * inode, struct dentry * dentry)
{
        int result;

        KHOOK_USAGE_INC(ext4_unlink);

        fstrash_unlink(inode, dentry);
        result = KHOOK_ORIGIN(ext4_unlink, inode, dentry);

        KHOOK_USAGE_DEC(ext4_unlink);

        return result;
}

Использование

Что касается использования, то один из возможных вариантов — это создание cron-задачи, выполняющейся с определённой периодичностью (например, 5 минут) и подчищающей, как вариант, всё, что лежит в корзине по маске /.fstrash/XXX_*. Время каждый может подобрать для себя сам как и то, по каким критериям чистить :)

Код данного проекта доступен github.

Автор: milabs

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js