• Linux 内核文件系统dentry_path_raw函数


    一、简介

    // 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    该函数根据给定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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    函数 __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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    dentry = d;
    end = buf + buflen;
    len = buflen;
    prepend(&end, &len, "\0", 1);
    /* Get '/' right */
    retval = end-1;
    *retval = '/';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    缓冲区buf 末尾会有一个斜杠 (/) 和一个空字符 (\0),形成一个以斜杠结尾的字符串,以确保路径名的正确格式。

    通过遍历 dentry 的父节点链来构建路径名。循环中,函数调用 prepend_name 函数将每个父节点的名称添加到路径名的前面。同时,函数更新 end 和 len 变量以反映已添加的路径名部分和剩余缓冲区长度。

    循环继续,直到遍历到根节点(使用 IS_ROOT 宏判断为真):

    #define IS_ROOT(x) ((x) == (x)->d_parent)
    
    • 1

    在此期间,函数通过调用 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    					 / 
      				   /c/
         		/user01/c/
    	   /home/user01/c/
    buf    retval
    
    • 1
    • 2
    • 3
    • 4
    • 5

    应该使用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    二、dmeo

    #include 
    #include 
    #include 
    #include 
    
    
    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");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    需要注意的是,调用者调用dentry_path_raw应该使用返回的指针来作为路径名,而不是传入的缓冲区指针。路径名通常是从缓冲区的部分字节偏移处开始的,缓冲区的开头部分字节是无意义的。

    			/usr/bin/kmod
    buffer		path
    
    • 1
    • 2

    应当使用返回的指针path来作为路径名,而不是传入的缓冲区指针buffer。

    # dmesg -c
    path = /usr/bin/kmod
    
    • 1
    • 2

    参考资料

    Linux 5.4.18

    https://blog.csdn.net/qq_42931917/article/details/119803534

  • 相关阅读:
    HTML之表格、表单
    c++实现建造者模式
    set 模拟与用法
    parallelStream的讲解
    PyTorch修改为自定义节点
    2.mysql的安装
    KylinV10系统如何查找JDK路径和安装JDK
    清洁、对话、带娃,扫地机摆脱“人工智障”标签
    计算机毕业设计ssm+vue基本微信小程序的购物商城小程序
    C++标准模板(STL)- 类型支持 (类型特性,is_union,is_class,is_function)
  • 原文地址:https://blog.csdn.net/weixin_45030965/article/details/134036078