Получите путь к каталогу fd

Я столкнулся с потребностью смочь, обращаются к каталогу путем, учитывая его дескриптор файла в Linux. Путь не должен быть каноническим, это просто должно быть функционально так, чтобы я мог передать его другим функциям. Так, беря те же параметры, как передано функции как fstatat(), Я должен смочь вызвать функцию как getxattr() который не имеет a f-XYZ-at() вариант.

До сих пор я предложил эти решения; хотя ни один не особенно изящен.

Простое решение состоит в том, чтобы избежать проблемы путем вызова openat() и затем с помощью функции как fgetxattr(). Это работает, но не в каждой ситуации. Таким образом, другой метод необходим для заполнения разрывов.

Следующее решение вовлекает поиск информации в proc:

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

Это, конечно, полностью повреждается в системах без proc, включая некоторые chroot среды.

Последняя опция, более портативное, но potentially-race-condition-prone решение, похожа на это:

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

11
задан tylerl 28 May 2012 в 06:14
поделиться

1 ответ

Ядро думает о каталогах иначе, чем вы - оно думает в терминах номеров inode. Он хранит запись номера inode (и номера устройства) для каталога, и это все, что ему нужно в качестве текущего каталога. Тот факт, что вы иногда указываете ему имя, означает, что он отслеживает номер inode, соответствующий этому имени, но сохраняет только номер inode, потому что это все, что ему нужно.

Итак, вам нужно будет написать подходящую функцию. Вы можете открыть каталог напрямую с помощью open () , чтобы получить дескриптор файла, который может использоваться fchdir () ; вы не можете делать с ним ничего другого во многих современных системах. Вы также можете не открыть текущий каталог; вы должны проверить этот результат. Обстоятельства, при которых это происходит, редки, но не исключены. (Программа SUID может chdir () перейти к каталогу, разрешенному привилегиями SUID, но затем отбросить привилегии SUID, в результате чего процесс не сможет прочитать каталог; вызов getcwd () будет терпят неудачу и в таких обстоятельствах - поэтому вы также должны проверить это на наличие ошибок!) Кроме того, если каталог будет удален, когда ваш (возможно, длительный) процесс открыл его, то последующая getcwd () завершится ошибкой .

Всегда проверяйте результаты системных вызовов; обычно бывают обстоятельства, при которых они могут потерпеть неудачу, даже если для них это ужасно неудобно. Есть исключения - getpid () является каноническим примером, но их очень мало.(Хорошо: не так уж и далеко - getppid () - еще один пример, и он чертовски близок к getpid () в руководстве и getuid () и родственники тоже не за горами.)

Многопоточные приложения представляют собой проблему; использование chdir () в них не очень хорошая идея. Возможно, вам придется выполнить fork () и попросить дочерний элемент оценить имя каталога, а затем каким-то образом передать это обратно родительскому объекту.


bignose спрашивает:

Это интересно, но, похоже, противоречит описанному кверенту опыту: этот getcwd знает, как получить путь из fd. Это указывает на то, что система знает, как перейти от fd к пути, по крайней мере, в некоторых ситуациях; можете ли вы отредактировать свой ответ, чтобы решить эту проблему?

Это помогает понять, как - или хотя бы один механизм - может быть написана функция getcwd () . Игнорируя проблему «нет разрешения», основной механизм, с помощью которого он работает:

  • Используйте stat в корневом каталоге «/» (чтобы вы знали, когда прекратить движение вверх).
  • Использовать stat в текущем каталоге '.' (чтобы вы знали, где находитесь); это дает вам текущий индексный дескриптор.
  • Пока вы не дойдете до корневого каталога:
  • Просканируйте родительский каталог '..', пока не найдете запись с тем же индексом, что и текущий индекс; это дает вам имя следующего компонента пути к каталогу.
  • А затем измените текущий индексный дескриптор на индексный дескриптор '.' в родительском каталоге.
  • Когда вы достигнете корня, вы можете построить путь.

Вот реализация этого алгоритма.Это старый код (изначально 1986 г .; последние некосметические изменения были внесены в 1998 г.) и не использует fchdir () должным образом. Он также ужасно работает, если у вас есть автоматически смонтированные файловые системы 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 */
8
ответ дан 3 December 2019 в 09:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: