[英]Hacking samba - how to get a directory from struct fd_handle
[英]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()
函數。 忽略“不允許”的問題,它運作的基本機制是:
這是該算法的實現。 它是舊代碼(最初是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.