簡體   English   中英

通過fd獲取目錄路徑

[英]Get directory path by fd

我已經遇到了在Linux中給出其文件描述符的路徑引用目錄的需要。 路徑不必是規范的,它必須是功能性的,以便我可以將它傳遞給其他功能。 因此,使用與傳遞給fstatat()類的函數相同的參數,我需要能夠調用像getxattr()這樣沒有f-XYZ-at()變體的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的方式是:它從相關的目錄條目開始,並將該目錄的父級名稱添加到路徑字符串中,並重復該過程直到它到達根目錄。 理論上可以在用戶空間中實現相同的機制。

感謝Jonathan Leffler指出這個算法。 以下是此函數的內核實現的鏈接: https//github.com/torvalds/linux/blob/v3.4/fs/dcache.c#L2577

內核認為目錄與你的方式不同 - 它根據inode數量來考慮。 它記錄了目錄的inode編號(和設備編號),這就是當前目錄所需的全部內容。 您有時為其指定名稱的事實意味着它會跟蹤並追蹤與該名稱對應的inode編號,但它僅保留inode編號,因為這就是它所需要的全部內容。

因此,您必須編寫合適的函數。 您可以使用open()直接打開目錄,以獲取fchdir()可以使用的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 */

Jonathan的回答非常精細,展示了它是如何運作的。 但它沒有顯示您描述的情況的解決方法。

我還想用你描述的東西:

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

但是,為了避免與線程中的競爭條件,fork另一個進程才能做到這一點。

這可能聽起來很昂貴,但如果你不經常這樣做,它應該沒問題。

這個想法是這樣的(沒有可運行的代碼,只是一個原始想法):

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);
}

我不確定這是否會解決所有問題,我也不會做任何錯誤檢查,所以這就是你應該添加的內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM