Получить путь к каталогу по fd - PullRequest
11 голосов
/ 21 марта 2010

Я столкнулся с необходимостью иметь возможность ссылаться на каталог по пути, учитывая его файловый дескриптор в 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 ]

8 голосов
/ 22 марта 2010

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

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

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

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


Бигноз спрашивает:

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

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

  • Используйте stat в корневом каталоге '/' (чтобы вы знали, когда прекратить движение вверх).
  • Использовать stat для текущего каталога '.' (чтобы вы знали, где вы находитесь); это дает вам текущий индекс.
  • Пока вы не достигнете корневого каталога:
  • Сканируйте родительский каталог '..', пока не найдете запись с тем же индексом, что и текущий индекс; это даст вам имя следующего компонента пути к каталогу.
  • А затем измените текущий индекс на индекс '.' в родительском каталоге.
  • Когда вы достигнете 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 */
4 голосов
/ 28 мая 2012

Ответ Джонатана очень хорошо показывает, как это работает. Но это не показывает обхода ситуации, которую вы описываете.

Я бы также использовал то, что вы описываете:

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

Я не уверен, что это решит все проблемы, и я также не проверяю ошибки, так что это то, что вы должны добавить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...