通过fd获取目录路径

tyl*_*erl 11 c unix linux posix

我已经遇到了在Linux中给出其文件描述符的路径引用目录的需要.路径不必是规范的,它必须是功能性的,以便我可以将它传递给其他功能.因此,采用与传递给函数相同的参数fstatat(),我需要能够调用一个getxattr()没有f-XYZ-at()变量的函数.

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

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

下一个解决方案涉及在proc中查找信息:

if (!access("/proc/self/fd",X_OK)) {
    sprintf(path,"/proc/self/fd/%i/",fd);
}
Run Code Online (Sandbox Code Playgroud)

当然,这完全打破了没有proc的系统,包括一些chroot环境.

最后一个选项,一个更便携但可能更具竞争条件的解决方案,如下所示:

DIR* save = opendir(".");
fchdir(fd);
getcwd(path,PATH_MAX);
fchdir(dirfd(save));
closedir(save);
Run Code Online (Sandbox Code Playgroud)

这里显而易见的问题是,在多线程应用程序中,更改工作目录可能会产生副作用.

然而,它工作的事实是令人信服的:如果我可以通过调用fchdir()后跟我来获取目录的路径getcwd(),为什么我不能直接获取信息:fgetcwd()或者其他东西.很明显,内核正在跟踪必要的信息.

那我该怎么做呢?


回答

Linux getcwd在内核中实现的方式是:它从相关的目录条目开始,并将该目录的父级名称添加到路径字符串中,并重复该过程直到它到达根目录.理论上可以在用户空间中实现相同的机制.

感谢Jonathan Leffler指出这个算法.以下是此函数的内核实现的链接:https://github.com/torvalds/linux/blob/v3.4/fs/dcache.c#L2577

Jon*_*ler 8

内核认为目录与你的方式不同 - 它根据inode数量来考虑.它记录了目录的inode编号(和设备编号),这就是当前目录所需的全部内容.您有时为其指定名称的事实意味着它会跟踪并追踪与该名称对应的inode编号,但它仅保留inode编号,因为这就是它所需要的全部内容.

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

始终检查系统调用的结果; 通常情况下他们可能会失败,即使他们这样做非常不方便.有例外 - getpid()是规范的例子 - 但它们很少而且很远.(好的:不是所有这一切 - getppid()是另一个例子,它getpid()在手册中非常接近; getuid()并且亲属在手册中也不远了.)

多线程应用程序是一个问题; 使用chdir()在那些不是一个好主意.您可能必须让fork()孩子评估目录名称,然后以某种方式将其传达回父级.


bignose问道:

这很有趣,但似乎违背了querent的报道经验:getcwd知道如何从fd获取路径.这表明系统至少在某些情况下知道如何从fd转到路径; 你可以编辑你的答案来解决这个问题吗?

为此,有助于理解如何 - 或至少一种机制 - getcwd()可以编写函数.忽略"不允许"的问题,它运作的基本机制是:

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

这是该算法的实现.它是旧代码(最初是1986年;最后一次非整形改变是在1998年),并没有fchdir()按照它应该使用.如果您使用NFS自动挂载的文件系统进行遍历,它也会非常糟糕 - 这就是我不再使用它的原因.但是,这大致相当于使用的基本方案getcwd().(哦;我看到一个18个字符的字符串("../123456789.abcd") - 好吧,回来它写的时候,我工作的机器只有14个字符的旧文件名 - 而不是现代的flex名称.就像我说的那样,它是旧的代码!我还没有看到其中一个文件系统,大概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 */
Run Code Online (Sandbox Code Playgroud)

  • 这非常有趣,让我思考.我去了一下内核源代码,看看**内核是如何进行getcwd查找的.所以你很有吸引力.我将它跟踪到fs/dcache.c中名为`__d_path`的函数.看看你自己:http://lxr.linux.no/#linux+v2.6.33/fs/dcache.c#L1905 (2认同)