是否可以在 C 中获取文件描述符(Linux)的文件名?
8 回答
您可以readlink
在/proc/self/fd/NNN
NNN 是文件描述符的地方使用。这将为您提供文件打开时的名称——但是,如果文件从那时起被移动或删除,它可能不再准确(尽管 Linux 在某些情况下可以跟踪重命名)。要验证stat
给定的文件名和您拥有的 fd,并fstat
确保它们是相同的。st_dev
st_ino
当然,并非所有文件描述符都引用文件,对于那些您会看到一些奇怪的文本字符串,例如pipe:[1538488]
. 由于所有真实文件名都是绝对路径,因此您可以很容易地确定哪些是绝对路径。此外,正如其他人所指出的,文件可以有多个指向它们的硬链接——这只会报告打开它的那个。如果要查找给定文件的所有名称,则只需遍历整个文件系统。
我在 Mac OS X 上遇到了这个问题。我们没有/proc
虚拟文件系统,所以接受的解决方案无法工作。
相反,我们有一个F_GETPATH
命令fcntl
:
F_GETPATH Get the path of the file descriptor Fildes. The argu-
ment must be a buffer of size MAXPATHLEN or greater.
因此,要获取与文件描述符关联的文件,您可以使用以下代码段:
#include <sys/syslimits.h>
#include <fcntl.h>
char filePath[PATH_MAX];
if (fcntl(fd, F_GETPATH, filePath) != -1)
{
// do something with the file path
}
因为我不记得在哪里MAXPATHLEN
定义了,所以我认为PATH_MAX
syslimits 会很好。
在 Windows 中,使用GetFileInformationByHandleEx,传递FileNameInfo,您可以检索文件名。
正如泰勒指出的那样,没有办法“直接可靠地”做你需要的事情,因为给定的 FD 可能对应于 0 个文件名(在各种情况下)或 > 1(多个“硬链接”是后一种情况的通常描述方式)。如果您仍然需要具有所有限制的功能(速度和获得 0、2、... 结果而不是 1 的可能性),那么您可以这样做:首先,fstat FD——这会告诉您,在结果struct stat
中,文件存在于什么设备上,它有多少硬链接,是否是特殊文件等。这可能已经回答了你的问题 - 例如,如果 0 硬链接你会知道实际上没有相应的文件名在磁盘上。
如果统计数据给了您希望,那么您必须在相关设备上“遍历目录树”,直到找到所有硬链接(或者只是第一个,如果您不需要多个硬链接,任何人都可以)。为此,您使用readdir(当然还有 opendir &c )递归地打开子目录,直到您在 a 中找到struct dirent
与原始目录相同的 inode 编号struct stat
(此时如果您想要整个路径,而不仅仅是名称,您需要向后遍历目录链以重建它)。
如果这种通用方法是可以接受的,但是您需要更详细的 C 代码,请告诉我们,它不会很难编写(尽管如果它没用,我宁愿不写它,即您无法承受不可避免的缓慢性能或为您的申请目的获得 != 1 个结果的可能性;-)。
在将其视为不可能之前,我建议您查看lsof命令的源代码。
可能有限制,但 lsof 似乎能够确定文件描述符和文件名。此信息存在于 /proc 文件系统中,因此应该可以从您的程序中获取。
您可以使用 fstat() 通过 struct stat 获取文件的 inode。然后,使用 readdir() 您可以将找到的 inode 与目录中存在的 inode (struct dirent) 进行比较(假设您知道该目录,否则您将不得不搜索整个文件系统)并找到相应的文件名。讨厌?
不可能的。文件描述符在文件系统中可能有多个名称,也可能根本没有名称。
编辑:假设您正在谈论一个普通的旧 POSIX 系统,没有任何特定于操作系统的 API,因为您没有指定操作系统。
在 OpenBSD 上没有官方 API 可以做到这一点,尽管有一些非常复杂的解决方法,但仍然可以使用以下代码,注意您需要链接-lkvm
和-lc
。使用 FTS 遍历文件系统的代码来自这个答案。
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <sys/stat.h>
#include <fts.h>
#include <sys/sysctl.h>
#include <kvm.h>
using std::string;
using std::vector;
string pidfd2path(int pid, int fd) {
string path; char errbuf[_POSIX2_LINE_MAX];
static kvm_t *kd = nullptr; kinfo_file *kif = nullptr; int cntp = 0;
kd = kvm_openfiles(nullptr, nullptr, nullptr, KVM_NO_FILES, errbuf); if (!kd) return "";
if ((kif = kvm_getfiles(kd, KERN_FILE_BYPID, pid, sizeof(struct kinfo_file), &cntp))) {
for (int i = 0; i < cntp; i++) {
if (kif[i].fd_fd == fd) {
FTS *file_system = nullptr; FTSENT *child = nullptr; FTSENT *parent = nullptr;
vector<char *> root; char buffer[2]; strcpy(buffer, "/"); root.push_back(buffer);
file_system = fts_open(&root[0], FTS_COMFOLLOW | FTS_NOCHDIR, nullptr);
if (file_system) {
while ((parent = fts_read(file_system))) {
child = fts_children(file_system, 0);
while (child && child->fts_link) {
child = child->fts_link;
if (!S_ISSOCK(child->fts_statp->st_mode)) {
if (child->fts_statp->st_dev == kif[i].va_fsid) {
if (child->fts_statp->st_ino == kif[i].va_fileid) {
path = child->fts_path + string(child->fts_name);
goto finish;
}
}
}
}
}
finish:
fts_close(file_system);
}
}
}
}
kvm_close(kd);
return path;
}
int main(int argc, char **argv) {
if (argc == 3) {
printf("%s\n", pidfd2path((int)strtoul(argv[1], nullptr, 10),
(int)strtoul(argv[2], nullptr, 10)).c_str());
} else {
printf("usage: \"%s\" <pid> <fd>\n", argv[0]);
}
return 0;
}
如果函数找不到文件(例如,因为它不再存在),它将返回一个空字符串。如果文件被移动,根据我在将文件移动到垃圾箱时的经验,如果 FTS 尚未搜索到该位置,则会返回文件的新位置。对于具有更多文件的文件系统,它会更慢。
在整个文件系统的目录树中搜索越深而没有找到文件,您就越有可能遇到竞争条件,尽管由于其性能如何仍然不太可能。我知道我的 OpenBSD 解决方案是 C++ 而不是 C。随意将其更改为 C,并且大部分代码逻辑都是相同的。如果我有时间,我会尽快用 C 重写它。与 macOS 一样,该解决方案随机获得一个硬链接(需要引用),以便在 Windows 和其他只能获得一个硬链接的平台上移植。如果您不关心跨平台并且想要获取所有硬链接,则可以删除 while 循环中的中断并返回一个向量。DragonFly BSD 和 NetBSD 与当前问题的 macOS 解决方案具有相同的解决方案(完全相同的代码),我手动验证。如果 macOS 用户希望通过插入进程 ID 从打开任何进程的文件描述符中获取路径,而不仅限于调用进程,同时还可能获取所有硬链接,而不限于随机一个,看到这个答案。它应该比遍历整个文件系统的性能要高得多,类似于它在 Linux 和其他更直接和直截了当的解决方案上的速度。FreeBSD 用户可以在这个问题中得到他们想要的东西,因为该问题中提到的操作系统级别的错误已经针对较新的操作系统版本得到解决。
这是一个更通用的解决方案,它只能检索调用进程打开的文件描述符的路径,但是它应该适用于大多数开箱即用的类 Unix,与前一个解决方案有关硬链接和竞争条件,尽管由于 if-then、for-loop 等较少,性能稍快:
#include <string>
#include <vector>
#include <cstring>
#include <sys/stat.h>
#include <fts.h>
using std::string;
using std::vector;
string fd2path(int fd) {
string path;
FTS *file_system = nullptr; FTSENT *child = nullptr; FTSENT *parent = nullptr;
vector<char *> root; char buffer[2]; strcpy(buffer, "/"); root.push_back(buffer);
file_system = fts_open(&root[0], FTS_COMFOLLOW | FTS_NOCHDIR, nullptr);
if (file_system) {
while ((parent = fts_read(file_system))) {
child = fts_children(file_system, 0);
while (child && child->fts_link) {
child = child->fts_link; struct stat info = { 0 };
if (!S_ISSOCK(child->fts_statp->st_mode)) {
if (!fstat(fd, &info) && !S_ISSOCK(info.st_mode)) {
if (child->fts_statp->st_dev == info.st_dev) {
if (child->fts_statp->st_ino == info.st_ino) {
path = child->fts_path + string(child->fts_name);
goto finish;
}
}
}
}
}
}
finish:
fts_close(file_system);
}
return path;
}
一个更快的解决方案也仅限于调用过程,但应该更高效,您可以使用辅助函数包装对 fopen() 和 open() 的所有调用,该辅助函数基本上存储与 std 等效的任何 C 语言: :unordered_map,并将文件描述符与传递给 fopen()/open() 包装器的绝对路径版本(以及在 UWP 上不起作用的仅 Windows 等效项,如 _wopen_s() 和所有废话)配对支持 UTF-8),这可以在类 Unix 上使用 realpath() 或在 Windows 上使用 GetFullPathNameW() (*W 表示 UTF-8 支持)来完成。realpath() 将解析符号链接(在 Windows 上不常用), realpath() / GetFullPathNameW() 会将您打开的现有文件从相对路径(如果是一个)转换为绝对路径。使用文件描述符和绝对路径存储一个与 std::unordered_map 等效的 C (您可能必须使用 malloc()'d 和最终 free()'d int 和 c-string 数组自己编写),这将再次,比任何其他动态搜索文件系统的解决方案都要快,但它有一个不同且不吸引人的限制,即它不会记录在文件系统上移动的文件,但至少你可以检查是否该文件已使用您自己的代码删除以测试是否存在,它也不会记录该文件自您打开它并将描述符的路径存储在内存中后是否被替换,从而可能会给您提供过时的结果。如果您想查看此代码示例,请告诉我,