文章目录
- 一、简介
- 1.1 __dentry_path
- 1.2 prepend_name
- 1.3 d_path
- 二、dmeo
- 参考资料
一、简介
// linux-5.4.18/fs/d_path.c
char *dentry_path_raw(struct dentry *dentry, char *buf, int buflen)
{
return __dentry_path(dentry, buf, buflen);
}
EXPORT_SYMBOL(dentry_path_raw);
该函数根据给定dentry返回其文件的完整路径。使用EXPORT_SYMBOL导出,可以直接调用。
关于dentry介绍请参考:Linux文件系统 struct dentry 结构体解析
1.1 __dentry_path
/*
* Write full pathname from the root of the filesystem into the buffer.
*/
static char *__dentry_path(struct dentry *d, char *buf, int buflen)
{
struct dentry *dentry;
char *end, *retval;
int len, seq = 0;
int error = 0;
if (buflen < 2)
goto Elong;
rcu_read_lock();
restart:
dentry = d;
end = buf + buflen;
len = buflen;
prepend(&end, &len, "\0", 1);
/* Get '/' right */
retval = end-1;
*retval = '/';
read_seqbegin_or_lock(&rename_lock, &seq);
while (!IS_ROOT(dentry)) {
struct dentry *parent = dentry->d_parent;
prefetch(parent);
error = prepend_name(&end, &len, &dentry->d_name);
if (error)
break;
retval = end;
dentry = parent;
}
if (!(seq & 1))
rcu_read_unlock();
if (need_seqretry(&rename_lock, seq)) {
seq = 1;
goto restart;
}
done_seqretry(&rename_lock, seq);
if (error)
goto Elong;
return retval;
Elong:
return ERR_PTR(-ENAMETOOLONG);
}
函数 __dentry_path,用于将文件系统根目录到给定的 dentry 的完整路径名写入缓冲区。
函数的参数包括要获取路径名的 dentry,存储路径名的缓冲区 buf,以及缓冲区的长度 buflen。
static int prepend(char **buffer, int *buflen, const char *str, int namelen)
{
*buflen -= namelen;
if (*buflen < 0)
return -ENAMETOOLONG;
*buffer -= namelen;
memcpy(*buffer, str, namelen);
return 0;
}
dentry = d;
end = buf + buflen;
len = buflen;
prepend(&end, &len, "\0", 1);
/* Get '/' right */
retval = end-1;
*retval = '/';
缓冲区buf 末尾会有一个斜杠 (/) 和一个空字符 (\0),形成一个以斜杠结尾的字符串,以确保路径名的正确格式。
通过遍历 dentry 的父节点链来构建路径名。循环中,函数调用 prepend_name 函数将每个父节点的名称添加到路径名的前面。同时,函数更新 end 和 len 变量以反映已添加的路径名部分和剩余缓冲区长度。
循环继续,直到遍历到根节点(使用 IS_ROOT 宏判断为真):
#define IS_ROOT(x) ((x) == (x)->d_parent)
在此期间,函数通过调用 prefetch 函数预取父节点,以提高性能。
_dentry_path 函数负责构建文件系统根目录到给定 dentry 对象的完整路径名,并将其存储在提供的缓冲区中。
注意_dentry_path 函数(即dentry_path_raw)对于传入的 buf 缓冲区,是从缓冲区的末尾开始构建dentry 对象的路径名,从buf的末尾往前构建dentry 对象的路径名,因此构建完成后,我们不能使用buf缓冲区来当作路径名,应该使用该函数的返回值来当作路径名,即调用者应该使用返回的指针来使用路径名,而不是传入的缓冲区指针。缓冲区的开头留下 0 字节(如果传入的缓冲区buf初始化为0),而路径名的开始通常会从缓冲区开始的部分字节偏移处开始的。
大概的过程就是,比如目录 /home/user01/c/:
//缓冲区buf末尾添加一个斜杠 (/) 和一个空字符 (\0),形成一个以斜杠结尾的字符串,以确保路径名的正确格式。
/\0
/c/\0
/user01/c/\0
/home/user01/c/\0
buf retval
/
/c/
/user01/c/
/home/user01/c/
buf retval
应该使用retval作为文件名而不是buf。
这里有一个疑问?如果是获取文件的完整路径,比如文件 /home/user01/c/text.txt,那么需要在text.txt的末尾添加一个斜杠 (/)?
如下图所示:
1.2 prepend_name
/**
* prepend_name - prepend a pathname in front of current buffer pointer
* @buffer: buffer pointer
* @buflen: allocated length of the buffer
* @name: name string and length qstr structure
*
* With RCU path tracing, it may race with d_move(). Use READ_ONCE() to
* make sure that either the old or the new name pointer and length are
* fetched. However, there may be mismatch between length and pointer.
* The length cannot be trusted, we need to copy it byte-by-byte until
* the length is reached or a null byte is found. It also prepends "/" at
* the beginning of the name. The sequence number check at the caller will
* retry it again when a d_move() does happen. So any garbage in the buffer
* due to mismatched pointer and length will be discarded.
*
* Load acquire is needed to make sure that we see that terminating NUL.
*/
static int prepend_name(char **buffer, int *buflen, const struct qstr *name)
{
const char *dname = smp_load_acquire(&name->name); /* ^^^ */
u32 dlen = READ_ONCE(name->len);
char *p;
*buflen -= dlen + 1;
if (*buflen < 0)
return -ENAMETOOLONG;
p = *buffer -= dlen + 1;
*p++ = '/';
while (dlen--) {
char c = *dname++;
if (!c)
break;
*p++ = c;
}
return 0;
}
prepend_name 静态函数用于在当前缓冲区指针的前面添加一个路径名。
函数的参数包括一个指向指针的指针 buffer,一个指向整数的指针 buflen,以及一个指向 struct qstr 的指针 name,其中 struct qstr 包含了要添加的路径名的字符串和长度信息。
函数的逻辑如下:
(1)首先,函数通过使用 smp_load_acquire 函数从 name->name 加载字符串指针,确保在读取指针和长度时不会发生竞争条件。
(2)接下来,函数使用 READ_ONCE 宏读取 name->len 的值。尽管会有指针和长度不匹配的情况,但长度不能被信任,因此需要逐字节复制,直到达到指定的长度或找到空字符为止。
(3)然后,函数根据要添加的路径名的长度将缓冲区指针向前移动,并将斜杠 / 添加到缓冲区的开头。
(4)在循环中,函数逐字节复制路径名的字符,直到达到指定的长度或找到空字符为止。
(5)最后,函数返回 0,表示添加路径名的操作成功。
该函数的目的是在当前缓冲区指针的前面添加一个路径名,并更新缓冲区指针和剩余长度。
1.3 d_path
与此函数功能相同的函数有:d_path。
/**
* d_path - return the path of a dentry
* @path: path to report
* @buf: buffer to return value in
* @buflen: buffer length
*
* Convert a dentry into an ASCII path name. If the entry has been deleted
* the string " (deleted)" is appended. Note that this is ambiguous.
*
* Returns a pointer into the buffer or an error code if the path was
* too long. Note: Callers should use the returned pointer, not the passed
* in buffer, to use the name! The implementation often starts at an offset
* into the buffer, and may leave 0 bytes at the start.
*
* "buflen" should be positive.
*/
char *d_path(const struct path *path, char *buf, int buflen)
{
char *res = buf + buflen;
struct path root;
int error;
/*
* We have various synthetic filesystems that never get mounted. On
* these filesystems dentries are never used for lookup purposes, and
* thus don't need to be hashed. They also don't need a name until a
* user wants to identify the object in /proc/pid/fd/. The little hack
* below allows us to generate a name for these objects on demand:
*
* Some pseudo inodes are mountable. When they are mounted
* path->dentry == path->mnt->mnt_root. In that case don't call d_dname
* and instead have d_path return the mounted path.
*/
if (path->dentry->d_op && path->dentry->d_op->d_dname &&
(!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root))
return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
rcu_read_lock();
get_fs_root_rcu(current->fs, &root);
error = path_with_deleted(path, &root, &res, &buflen);
rcu_read_unlock();
if (error < 0)
res = ERR_PTR(error);
return res;
}
EXPORT_SYMBOL(d_path);
二、dmeo
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
static int __init hello_init(void)
{
char *buffer, *path;
buffer = (char *)__get_free_page(GFP_KERNEL);
if (!buffer)
return 0;
path = dentry_path_raw(current->mm->exe_file->f_path.dentry, buffer, PAGE_SIZE);
if (IS_ERR(path)){
return 0;
}
printk("path = %s\n", path);
free_page((unsigned long)buffer);
return 0;
}
static void __exit hello_exit(void)
{
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
需要注意的是,调用者调用dentry_path_raw应该使用返回的指针来作为路径名,而不是传入的缓冲区指针。路径名通常是从缓冲区的部分字节偏移处开始的,缓冲区的开头部分字节是无意义的。
/usr/bin/kmod
buffer path
应当使用返回的指针path来作为路径名,而不是传入的缓冲区指针buffer。
# dmesg -c
path = /usr/bin/kmod
参考资料
Linux 5.4.18
https://blog.csdn.net/qq_42931917/article/details/119803534