Я столкнулся с потребностью смочь, обращаются к каталогу путем, учитывая его дескриптор файла в 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
Ядро думает о каталогах иначе, чем вы - оно думает в терминах номеров inode. Он хранит запись номера inode (и номера устройства) для каталога, и это все, что ему нужно в качестве текущего каталога. Тот факт, что вы иногда указываете ему имя, означает, что он отслеживает номер inode, соответствующий этому имени, но сохраняет только номер inode, потому что это все, что ему нужно.
Итак, вам нужно будет написать подходящую функцию. Вы можете открыть каталог напрямую с помощью open ()
, чтобы получить дескриптор файла, который может использоваться fchdir ()
; вы не можете делать с ним ничего другого во многих современных системах. Вы также можете не открыть текущий каталог; вы должны проверить этот результат. Обстоятельства, при которых это происходит, редки, но не исключены. (Программа SUID может chdir ()
перейти к каталогу, разрешенному привилегиями SUID, но затем отбросить привилегии SUID, в результате чего процесс не сможет прочитать каталог; вызов getcwd ()
будет терпят неудачу и в таких обстоятельствах - поэтому вы также должны проверить это на наличие ошибок!) Кроме того, если каталог будет удален, когда ваш (возможно, длительный) процесс открыл его, то последующая getcwd ()
завершится ошибкой .
Всегда проверяйте результаты системных вызовов; обычно бывают обстоятельства, при которых они могут потерпеть неудачу, даже если для них это ужасно неудобно. Есть исключения - getpid ()
является каноническим примером, но их очень мало.(Хорошо: не так уж и далеко - getppid ()
- еще один пример, и он чертовски близок к getpid ()
в руководстве и getuid ()
и родственники тоже не за горами.)
Многопоточные приложения представляют собой проблему; использование chdir ()
в них не очень хорошая идея. Возможно, вам придется выполнить fork ()
и попросить дочерний элемент оценить имя каталога, а затем каким-то образом передать это обратно родительскому объекту.
bignose спрашивает:
Это интересно, но, похоже, противоречит описанному кверенту опыту: этот getcwd знает, как получить путь из fd. Это указывает на то, что система знает, как перейти от fd к пути, по крайней мере, в некоторых ситуациях; можете ли вы отредактировать свой ответ, чтобы решить эту проблему?
Это помогает понять, как - или хотя бы один механизм - может быть написана функция getcwd ()
. Игнорируя проблему «нет разрешения», основной механизм, с помощью которого он работает:
Вот реализация этого алгоритма.Это старый код (изначально 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 */