📜 ⬆️ ⬇️

Dynamic extension of the Linux kernel - add the function "delete to the basket"

Many Linux users, especially those who for one reason or another have switched to this OS from Windows, lack the ability to delete files "to the trash." In addition, for sure, everyone who used Linux and accidentally deleted a file experienced mixed feelings from the lack of a simple opportunity to recover lost data.

In continuation of the previous material on the interception of functions of the Linux kernel, I present a way to use the framework developed earlier to create a Linux kernel module that implements the ability to delete files “to the recycle bin” (just for fun).


')

Formulation of the problem



We will strive to create the ability to delete files in such a way that they fall into the "basket". At the same time, a special directory in the root of the file system ( .fstrash ) will act as a basket. The essence of moving to the trash will be to ensure that at the time of deletion of the file to create for him a "hard link" in this directory. In this case, the delete operation, following the operation of creating a hard link, will delete only the “old name” of the file, but not its contents.

So, in order to be able to delete in the basket, it is necessary:



Implementation



To begin with, consider the fstrash_unlink function, which implements the logic of working with the basket:

 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; } 


First of all, this function checks the reference count ( i_nlink ) on the dentry element describing the file. Comparison with the unit is necessary in order to make sure that the requested operation (deletion) leads to the deletion of the file, since The i_nlink counter contains the number of directory entries that refer to this file (the number of hard links). Obviously, the value "1" corresponds to the only (last) link.

Further, the search for “basket” is performed and, if it is found and the requested delete operation is not an operation to delete from the basket itself (check trash->d_inode != inode ), the function of creating a hard link is called (moving the file to the basket) - 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; } 


As you can see, this function occurs:


In case of successful performance of the above operations, the object is marked as “dirty” using the mark_inode_dirty function, which signals the system to synchronize the metadata of this file.

Thus, in accordance with the method of embedding into the kernel considered earlier , interception of the key removal function for ext4 ext4_unlink filesystem will look like this:

 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; } 


It is also worth noting that the intercepted ext4_unlink function ext4_unlink called by the kernel from the vfs_unlink function, which in turn performs the necessary synchronization, preventing competitive access to information for the duration of the operation:

 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 } 


Thus, the i_op->unlink(...) operation is protected by the corresponding synchronization primitive ( d_inode->i_mutex ).

Using



As for use, one of the possible options is to create a cron task that runs at regular intervals (for example, 5 minutes) and cleans up, as an option, everything that lies in the basket by mask /.fstrash/XXX_* . Everyone can choose the time for himself as well as by what criteria to clean :)

The code for this project is available github .

Source: https://habr.com/ru/post/210364/


All Articles