代码之家  ›  专栏  ›  技术社区  ›  tylerl

按fd获取目录路径

  •  11
  • tylerl  · 技术社区  · 16 年前

    在Linux中,我遇到了通过给定文件描述符的路径来引用目录的需求。路径不必是规范的,它只需要是函数的,这样我就可以把它传递给其他函数。因此,使用传递给函数的相同参数 fstatat() ,我需要能够调用 getxattr() 它没有 f-XYZ-at() 变体。

    到目前为止,我已经提出了这些解决方案;尽管没有一个特别优雅。

    最简单的解决方案是通过调用 openat() 然后使用类似的函数 fgetxattr() . 这是可行的,但不是在所有情况下。因此,需要另一种方法来填补这些空白。

    下一个解决方案包括查找proc中的信息:

    if (!access("/proc/self/fd",X_OK)) {
        sprintf(path,"/proc/self/fd/%i/",fd);
    }
    

    当然,这在没有proc的系统上完全中断,包括一些chroot环境。

    最后一个选项,一个更便携但可能更容易出现竞争情况的解决方案,如下所示:

    DIR* save = opendir(".");
    fchdir(fd);
    getcwd(path,PATH_MAX);
    fchdir(dirfd(save));
    closedir(save);
    

    这里明显的问题是,在多线程应用程序中,更改周围的工作目录可能会产生副作用。

    但是,它的工作是令人信服的:如果我可以通过调用 fchdir() 然后 getcwd() ,为什么我不能直接获取信息: fgetcwd() 或者什么的。显然,内核正在跟踪必要的信息。

    那我该怎么做呢?


    回答

    Linux实现的方式 getcwd 在内核中是这样的:它从有问题的目录条目开始,并将该目录的父目录的名称前置到路径字符串,然后重复该过程,直到到达根目录。同样的机制理论上也可以在用户空间中实现。

    多亏了 乔纳森·莱弗勒 为了指出这个算法。以下是此函数的内核实现的链接: https://github.com/torvalds/linux/blob/v3.4/fs/dcache.c#L2577

    2 回复  |  直到 9 年前
        1
  •  8
  •   Jonathan Leffler    14 年前

    内核对目录的看法与您的看法不同——它考虑的是inode数字。它为目录保存一个inode编号(和设备编号)的记录,这就是它作为当前目录所需要的全部内容。您有时会为它指定一个名称,这意味着它会跟踪与该名称对应的inode编号,但它只保留inode编号,因为这就是它所需要的全部。

    因此,您必须编写一个合适的函数。您可以直接用打开目录 open() 精确地得到一个文件描述符 fchdir() 在许多现代系统中,你不能用它做任何其他事情。您也可能无法打开当前目录;您应该测试该结果。这种情况很少发生,但并不存在。(SUID程序可能 chdir() 到suid权限允许的目录,然后删除suid权限,使进程无法读取该目录;the getcwd() 在这种情况下,调用也会失败-因此您必须错误地检查它!)另外,如果在(可能是长时间运行的)进程打开某个目录时删除了该目录,则 getcwd()。 会失败。

    总是检查来自系统调用的结果;通常情况下,它们可能会失败,即使这样做非常不方便。有例外- getpid() 是典型的例子——但它们很少而且相距甚远。(好吧:不是很遥远- getppid() 是另一个例子,它非常接近 GETPID.() 在手册中;以及 getuid() 在手册中亲属也不远。)

    多线程应用程序是一个问题;使用 CHDIR() 这不是个好主意。你可能不得不 fork() 让孩子评估目录名,然后以某种方式将其传回父目录。


    比格斯问道:

    这很有趣,但似乎与Querent报告的经验相反:getcwd知道如何从fd获取路径。这表明系统至少在某些情况下知道如何从fd转到path;您可以编辑您的答案来解决这个问题吗?

    为此,它有助于理解 GETCWD() 可以写入函数。忽略“无权限”的问题,其工作的基本机制是:

    • 在根目录“/”上使用stat(这样您就知道何时停止向上移动)。
    • 在当前目录“”上使用stat(这样您就知道自己在哪里);这会给您一个当前的inode。
    • 直到到达根目录:
    • 扫描父目录“..”,直到找到与当前inode具有相同inode的条目;这将为您提供目录路径的下一个组件名称。
    • 然后将当前inode更改为父目录中“.”的inode。
    • 当您到达根目录时,您可以构建路径。

    下面是该算法的一个实现。它是旧的代码(最初是1986年,最后一次非整容性的修改是在1998年),没有使用 FCH() 应该如此。如果您有要遍历的NFS自动安装文件系统,它也会工作得很糟糕——这就是为什么我不再使用它的原因。然而,这大致相当于 GETCWD() . (噢,我看到一个18个字符的字符串(“../123456789.abcd”)—好吧,在写它的时候,我工作的机器只有非常老的14个字符的文件名—而不是现代的灵活名称。就像我说的,这是旧密码!我在15年左右的时间里,甚至更长的时间里,都没有见过这样的文件系统。还有一些代码可以处理较长的名称。小心使用。)


    /*
    @(#)File:           $RCSfile: getpwd.c,v $
    @(#)Version:        $Revision: 2.5 $
    @(#)Last changed:   $Date: 2008/02/11 08:44:50 $
    @(#)Purpose:        Evaluate present working directory
    @(#)Author:         J Leffler
    @(#)Copyright:      (C) JLSS 1987-91,1997-98,2005,2008
    @(#)Product:        :PRODUCT:
    */
    
    /*TABSTOP=4*/
    
    #define _POSIX_SOURCE 1
    
    #include "getpwd.h"
    
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    
    #if defined(_POSIX_SOURCE) || defined(USG_DIRENT)
    #include "dirent.h"
    #elif defined(BSD_DIRENT)
    #include <sys/dir.h>
    #define dirent direct
    #else
    What type of directory handling do you have?
    #endif
    
    #define DIRSIZ      256
    
    typedef struct stat   Stat;
    
    static Stat root;
    
    #ifndef lint
    /* Prevent over-aggressive optimizers from eliminating ID string */
    const char jlss_id_getpwd_c[] = "@(#)$Id: getpwd.c,v 2.5 2008/02/11 08:44:50 jleffler Exp $";
    #endif /* lint */
    
    /* -- Routine: inode_number */
    
    static ino_t   inode_number(char *path, char *name)
    {
        ino_t           inode;
        Stat            st;
        char            buff[DIRSIZ + 6];
    
        strcpy(buff, path);
        strcat(buff, "/");
        strcat(buff, name);
        if (stat(buff, &st))
            inode = 0;
        else
            inode = st.st_ino;
        return(inode);
    }
    
    /*
        -- Routine: finddir
        Purpose:    Find name of present working directory
    
        Given:
            In:  Inode of current directory
            In:  Device for current directory
            Out: pathname of current directory
            In:  Length of buffer for pathname
    
        Maintenance Log
        ---------------
        10/11/86  JL    Original version stabilised
        25/09/88  JL    Rewritten to use opendir/readdir/closedir
        25/09/90  JL    Modified to pay attention to length
        10/11/98  JL    Convert to prototypes
    
    */
    static int finddir(ino_t inode, dev_t device, char *path, size_t plen)
    {
        register char  *src;
        register char  *dst;
        char           *end;
        DIR            *dp;
        struct dirent  *d_entry;
        Stat            dotdot;
        Stat            file;
        ino_t           d_inode;
        int             status;
        static char     name[] = "../123456789.abcd";
        char            d_name[DIRSIZ + 1];
    
        if (stat("..", &dotdot) || (dp = opendir("..")) == 0)
            return(-1);
        /* Skip over "." and ".." */
        if ((d_entry = readdir(dp)) == 0 ||
            (d_entry = readdir(dp)) == 0)
        {
            /* Should never happen  */
            closedir(dp);
            return(-1);
        }
    
        status = 1;
        while (status)
        {
            if ((d_entry = readdir(dp)) == 0)
            {
                /* Got to end of directory without finding what we wanted */
                /* Probably a corrupt file system */
                closedir(dp);
                return(-1);
            }
            else if ((d_inode = inode_number("..", d_entry->d_name)) != 0 &&
                     (dotdot.st_dev != device))
            {
                /* Mounted file system */
                dst = &name[3];
                src = d_entry->d_name;
                while ((*dst++ = *src++) != '\0')
                    ;
                if (stat(name, &file))
                {
                    /* Can't stat this file */
                    continue;
                }
                status = (file.st_ino != inode || file.st_dev != device);
            }
            else
            {
                /* Ordinary directory hierarchy */
                status = (d_inode != inode);
            }
        }
        strncpy(d_name, d_entry->d_name, DIRSIZ);
        closedir(dp);
    
        /**
        ** NB: we have closed the directory we are reading before we move out of it.
        ** This means that we should only be using one extra file descriptor.
        ** It also means that the space d_entry points to is now invalid.
        */
        src = d_name;
        dst = path;
        end = path + plen;
        if (dotdot.st_ino == root.st_ino && dotdot.st_dev == root.st_dev)
        {
            /* Found root */
            status = 0;
            if (dst < end)
                *dst++ = '/';
            while (dst < end && (*dst++ = *src++) != '\0')
                ;
        }
        else if (chdir(".."))
            status = -1;
        else
        {
            /* RECURSE */
            status = finddir(dotdot.st_ino, dotdot.st_dev, path, plen);
            (void)chdir(d_name);    /* We've been here before */
            if (status == 0)
            {
                while (*dst)
                    dst++;
                if (dst < end)
                    *dst++ = '/';
                while (dst < end && (*dst++ = *src++) != '\0')
                    ;
            }
        }
    
        if (dst >= end)
            status = -1;
        return(status);
    }
    
    /*
        -- Routine: getpwd
    
        Purpose:    Evaluate name of current directory
    
        Maintenance Log
        ---------------
        10/11/86  JL    Original version stabilised
        25/09/88  JL    Short circuit if pwd = /
        25/09/90  JL    Revise interface; check length
        10/11/98  JL    Convert to prototypes
    
        Known Bugs
        ----------
        1.  Uses chdir() and could possibly get lost in some other directory
        2.  Can be very slow on NFS with automounts enabled.
    
    */
    char    *getpwd(char *pwd, size_t plen)
    {
        int             status;
        Stat            here;
    
        if (pwd == 0)
            pwd = malloc(plen);
        if (pwd == 0)
            return (pwd);
    
        if (stat("/", &root) || stat(".", &here))
            status = -1;
        else if (root.st_ino == here.st_ino && root.st_dev == here.st_dev)
        {
            strcpy(pwd, "/");
            status = 0;
        }
        else
            status = finddir(here.st_ino, here.st_dev, pwd, plen);
        if (status != 0)
            pwd = 0;
        return (pwd);
    }
    
    #ifdef TEST
    
    #include <stdio.h>
    
    /*
        -- Routine: main
        Purpose:    Test getpwd()
    
        Maintenance Log
        ---------------
        10/11/86  JL    Original version stabilised
        25/09/90  JL    Modified interface; use GETCWD to check result
    
    */
    int main(void)
    {
        char            pwd[512];
        int             pwd_len;
    
        if (getpwd(pwd, sizeof(pwd)) == 0)
            printf("GETPWD failed to evaluate pwd\n");
        else
            printf("GETPWD: %s\n", pwd);
        if (getcwd(pwd, sizeof(pwd)) == 0)
            printf("GETCWD failed to evaluate pwd\n");
        else
            printf("GETCWD: %s\n", pwd);
        pwd_len = strlen(pwd);
        if (getpwd(pwd, pwd_len - 1) == 0)
            printf("GETPWD failed to evaluate pwd (buffer is 1 char short)\n");
        else
            printf("GETPWD: %s (but should have failed!!!)\n", pwd);
        return(0);
    }
    
    #endif /* TEST */
    
        2
  •  4
  •   Jonathan Leffler    9 年前

    乔纳森的回答很好地说明了它是如何工作的。但是它没有显示出你描述的情况下的解决方法。

    我还想用你描述的东西:

    DIR* save = opendir(".");
    fchdir(fd);
    getcwd(path,PATH_MAX);
    fchdir(dirfd(save));
    closedir(save);
    

    但是,为了避免在线程中出现争用条件,需要分叉另一个进程来实现这一点。

    这听起来可能很贵,但如果你不经常这样做,应该没问题。

    想法是这样的(没有可运行的代码,只是一个原始想法):

    int fd[2];
    pipe(fd);
    pid_t pid;
    if ((pid = fork()) == 0) {
        // child; here we do the chdir etc. stuff
        close(fd[0]); // read end
        char path[PATH_MAX+1];
        DIR* save = opendir(".");
        fchdir(fd);
        getcwd(path,PATH_MAX);
        fchdir(dirfd(save));
        closedir(save);
        write(fd[1], path, strlen(path));
        close(fd[1]);
        _exit(EXIT_SUCCESS);
    } else {
        // parent; pid is our child
        close(fd[1]); // write end
        int cursor=0;
        while ((r=read(fd[0], &path+cursor, PATH_MAX)) > 0) {
            cursor += r;
        }
        path[cursor]='\0'; // make it 0-terminated
        close(fd[0]);
        wait(NULL);
    }
    

    我不确定这是否能解决所有问题,我也不做任何错误检查,所以这就是您应该添加的内容。

    推荐文章