一、背景
在之前的博客 缺页异常导致的iowait打印出相关文件的绝对路径-CSDN博客 里的 2.2.3 一节和 关于inode,dentry结合软链接及硬链接的实验-CSDN博客 里,我们讲到了在内核里通过inode获取inode对应的绝对路径的方法。对于根目录下的文件而言,这也是足够了,但是对于非根目录下的文件即有过挂载后的文件,这就不够了。我们后面的博客会讲到,我们有时候比如在shm相关的监控场景下,还是需要知道dentry的super_block的挂载目录的。
我们在第二章里先贴出能获取inode的dentry的包含挂载路径的完整路径的源码,并演示成果。在第三章里,我们给出相关的源码解释及原理分析。
二、源码及成果展示
2.1 内核部分的改动
在fs/namespace.c里添加一个export symbol的函数get_dentry_by_sb_currns:
struct dentry* get_dentry_by_sb_currns(struct super_block *sb)
{struct mnt_namespace *mnt_ns = current->nsproxy->mnt_ns;struct mount *mnt;struct dentry* dentry = NULL;lock_mount_hash();list_for_each_entry(mnt, &mnt_ns->list, mnt_list) {if (mnt->mnt.mnt_sb == sb) {dentry = mnt->mnt_mountpoint;break;}}unlock_mount_hash();return dentry;
}
EXPORT_SYMBOL_GPL(get_dentry_by_sb_currns);
2.2 内核模块代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/namei.h>// 模块参数
static char *filepath = "/tmp/testfile"; // 默认文件路径
module_param(filepath, charp, S_IRUGO);
MODULE_PARM_DESC(filepath, "Path of the file to open");char buffer[4096];
char buffer_mount1[4096];
char buffer_mount2[4096];
char buffer_mount3[4096];extern struct dentry* get_dentry_by_sb_currns(struct super_block *sb);int getfullpath(struct inode *inode)
{struct dentry *dentry;printk("inode = %p\n", inode);hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) {char *path;path = dentry_path_raw(dentry, buffer, 4096);if (IS_ERR(path)){continue; }printk("dentry name = %s , path = %s\n", dentry->d_name.name, path);{struct dentry* de = get_dentry_by_sb_currns(inode->i_sb);char *path1 = dentry_path_raw(de, buffer_mount1, 4096);char *path2 = NULL;char *path3 = NULL;printk("path1 = %s\n", path1);if (de->d_sb) {if (strcmp(path1, "/") != 0) {de = get_dentry_by_sb_currns(de->d_sb);path2 = dentry_path_raw(de, buffer_mount2, 4096);printk("path2 = %s\n", path2);if (de->d_sb) {if (strcmp(path2, "/") != 0) {de = get_dentry_by_sb_currns(de->d_sb);path3 = dentry_path_raw(de, buffer_mount3, 4096);printk("path3 = %s\n", path3);}else {path2 = NULL;}}}else {path1 = NULL;}}if (path2) {printk("dentry name = %s%s%s\n", path2, path1, path);}else if (path1) {printk("dentry name = %s%s\n", path1, path);}else {printk("dentry name = %s\n", path);}}}return 0;
}static int __init my_module_init(void) {struct file *file;printk(KERN_INFO "Opening file: %s\n", filepath);// 打开文件file = filp_open(filepath, O_RDONLY, 0);printk(KERN_INFO "file[%p]\n", file);if (IS_ERR(file)) {printk(KERN_ERR "Error opening file: %ld\n", PTR_ERR(file));return PTR_ERR(file);}getfullpath(file->f_inode);// do {// char *path;// path = dentry_path_raw(file->f_path.dentry, buffer, PAGE_SIZE);// if (IS_ERR(path)){// break;// }// printk("[2] dentry name = %s , path = %s\n", file->f_path.dentry->d_name.name, path);// } while(0);// 关闭文件filp_close(file, NULL);return -EINVAL;
}static void __exit my_module_exit(void) {printk(KERN_INFO "Module exiting\n");
}module_init(my_module_init);
module_exit(my_module_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zhaoxin");
MODULE_DESCRIPTION("A simple module to read full path dentry from a file's inode");
2.3 成果展示
上面的模块代码只是为了打通演示,所以只是覆盖了两级mount的情况,我们下面展示根目录下的文件、mount过一级的目录下文件、mount过两级的目录下的文件,这三种情况:
2.3.1 根目录下的文件的情况
insmod testinode_fullpath.ko filepath=/home/aaa/zhaoxin/test.txt
运行后的dmesg的内容:
2.3.2 mount过一级的目录下的文件
insmod testinode_fullpath.ko filepath=/bigvolume/aa.txt
运行后的dmesg的内容:
2.3.3 mount过两级的目录下的文件
insmod testinode_fullpath.ko filepath=/dev/shm/aaa.txt
运行后的dmesg的内容:
三、源码解释及原理
3.1 增加的get_dentry_by_sb_currns函数的实现原理
之所以要把get_dentry_by_sb_currns函数放到fs/namespace.c里,是因为fs目录下的文件比如namespace.c文件包含了struct mount的定义,struct mount的定义是定义在fs目录下的:
该fs/mount.h属于fs下使用的一个结构体。不同于下图里的include/linux/mount.h(下图里的include/linux/mount.h是公共的mount.h,里面是没有包含struct mount的定义的):
看一下我们加的get_dentry_by_sb_currns函数的实现:
如上图红框,用的是当前任务的namespace里的mnt_ns来作为mount路径目录的namespace。
遍历该mnt_ns里的list来找匹配当前super_block的,并把mount里的mnt_mountpoint这个表示挂载位置的dentry给返回出来。
3.2 内核模块里使用get_dentry_by_sb_currns获取挂载点的路径
回到内核模块里,内核模块的代码其实是改造自之前的博客 关于inode,dentry结合软链接及硬链接的实验-CSDN博客 里的代码,在遍历查询的inode的所有dentry时,不仅通过dentry_path_raw获取到所属的super_block下的全路径之外,还获取super_block的挂载点的dentry路径,用的是get_dentry_by_sb_currns函数,如果发现挂载点的路径不是"/"的话,就继续找挂载点dentry的super_block的上一级挂载点,直到挂载点的dentry路径是"/"为止:
要注意,需要用挂载点的路径的字符串和"/"来比较,单用挂载点的d_sb的指针是不是NULL是不行的。