Многим пользователям Linux, особенно тем, кто по тем или иным причинам перешёл на эту ОС с Windows, не хватает возможности удаления файлов «в корзину». Кроме того, наверняка, каждый, кто пользовался Linux'ом и по ошибке удалял какой-либо файл, испытывал смешанные чувства от отсутствия простой возможности восстановить утраченные данные.
В продолжение предшествующего материала, посвящённого перехвату функций ядра Linux, представляю способ использования разработанного ранее фреймворка для создания модуля ядра Linux, реализующего возможность удаления файлов «в корзину» (just for fun).
Будем стремиться создать возможность удаления файлов таким образом, чтобы они попадали в «корзину». При этом, в роли корзины будет выступать специальный каталог, находящийся в корне файловой системы (
Итак, для того, чтобы иметь возможность удаления в корзину, необхдимо:
Начнём с того, что рассмотрим функцию fstrash_unlink, реализующую логику работы с корзиной:
В первую очередь, данная функция проверяет счётчик ссылок (
Далее, осуществляется поиск «корзины» и, в случае, если она обнаружена и запрашиваемая операция удаления не является операцией удаления из самой корзины (проверка
Как видно, в этой функции происходит:
В случае успешного выполнения перечисленных операций, объкт отмечается как «грязный» при помощи функции
Таким образом, в соответствии с рассматриваемым ранее способом встраивания в ядро, перехват ключевой функции удаления для ФС ext4 ext4_unlink будет выглядеть следующим образом:
Стоит отметить также, что перехватываемая функция
Таким образом, операция
Что касается использования, то один из возможных вариантов — это создание
Код данного проекта доступен github.
В продолжение предшествующего материала, посвящённого перехвату функций ядра 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;
}
Стоит отметить также, что перехватываемая функция
ext4_unlink
вызывается ядром из функции vfs_unlink, которая в свою очередь осуществляет необходимую синхронизацию, предотвращая конкурентный доступ к информации на время выполнения операции:3398 int vfs_unlink(struct inode *dir, struct dentry *dentry)
3399 {
...
3408 mutex_lock(&dentry->d_inode->i_mutex);
3409 if (d_mountpoint(dentry))
3410 error = -EBUSY;
3411 else {
3412 error = security_inode_unlink(dir, dentry);
3413 if (!error) {
3414 error = dir->i_op->unlink(dir, dentry);
3415 if (!error)
3416 dont_mount(dentry);
3417 }
3418 }
3419 mutex_unlock(&dentry->d_inode->i_mutex);
...
3428 }
Таким образом, операция
i_op->unlink(...)
защищена соответствующим примитивом синхронизации (d_inode->i_mutex
).Использование
Что касается использования, то один из возможных вариантов — это создание
cron
-задачи, выполняющейся с определённой периодичностью (например, 5 минут) и подчищающей, как вариант, всё, что лежит в корзине по маске /.fstrash/XXX_*
. Время каждый может подобрать для себя сам как и то, по каким критериям чистить :)Код данного проекта доступен github.