![](/img/trans.png)
[英]C program returns bad file descriptor on creating file with char * as filename
[英]Retrieve filename from file descriptor in C
是否可以在 C 中获取文件描述符(Linux)的文件名?
" 您可以在/proc/self/fd/NNN
上使用readlink
,其中NNN是文件描述符。 这将为您提供文件打开时的名称-但是,如果此后文件被移动或删除,它可能不再准确(尽管Linux在某些情况下可以跟踪重命名)。 要进行验证,请stat
给定的文件名和fstat
所拥有的fd,并确保st_dev
和st_ino
相同。
当然,并非所有文件描述符都引用文件,对于那些文件描述符,您会看到一些奇怪的文本字符串,例如pipe:[1538488]
。 由于所有真实文件名都是绝对路径,因此您可以轻松确定其中的哪个。 此外,正如其他人指出的那样,文件可以具有指向它们的多个硬链接-这只会报告打开文件的那个。 如果要查找给定文件的所有名称,则只需遍历整个文件系统。
我在Mac OS X上遇到了这个问题。我们没有/proc
虚拟文件系统,因此接受的解决方案无法正常工作。
相反,我们对fcntl
使用了F_GETPATH
命令:
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
的定义位置,因此我认为syslimits中的PATH_MAX
会很好。
在Windows中,通过GetFileInformationByHandleEx传递FileNameInfo ,您可以检索文件名。
正如泰勒(Tyler)指出的那样,由于给定的FD可能对应于0个文件名(在各种情况下)或> 1(多个“硬链接”通常是对后一种情况的描述),因此无法“直接且可靠地”执行所需的操作)。 如果您仍然需要具有所有限制的功能(在速度上以及获得0、2 ...结果而不是1的可能性),则可以按照以下方法进行操作:首先,将FD fstat告诉您- ,在生成的struct stat
,该文件位于哪个设备上,它具有多少个硬链接,它是否是一个特殊文件等。这可能已经回答了您的问题-例如,如果0个硬链接您将知道实际上没有磁盘上的相应文件名。
如果统计数据给了您希望,那么您必须在相关设备上“遍历目录”,直到找到所有硬链接(或者如果您不需要多个硬链接,则找到第一个硬链接,任何人都可以) )。 为此,您使用readdir (当然还有opendir&c)以递归方式打开子目录,直到您在struct dirent
找到从而收到与原始struct stat
相同的索引节点号(此时需要完整路径,而不是整个路径)。只是名称,您需要向后移动目录链以进行重构)。
如果可以接受这种通用方法,但是您需要更详细的C代码,请告诉我们,这将很容易编写(尽管如果它没有用,我宁愿不编写它,即您无法承受不可避免的性能下降或就您的应用而言,有可能获得!= 1个结果;-)。
您可以使用fstat()通过struct stat获取文件的inode。 然后,使用readdir()可以将找到的索引节点与目录中存在的索引索引(结构Dirent)进行比较(假定您知道该目录,否则必须搜索整个文件系统)并找到相应的文件名。 讨厌?
不可能。 文件描述符在文件系统中可能具有多个名称,也可能根本没有名称。
编辑:假设您正在谈论的是普通的旧POSIX系统,没有任何特定于操作系统的API,因为您没有指定操作系统。
经过一整天的研究和查看 OpenBSD 的源代码,我发现在 OpenBSD 上没有官方 API 可以执行此操作,尽管使用复杂的解决方法,仍然可以使用以下代码,注意这是从一个在我的存储库中,并非所有必要的标头都存在,并且您需要与-lkvm
链接,静态函数和全局变量也只是可重用的助手,您应该实际调用以获取文件名的函数是std::string file_bin_pathname(int fd)
,我采用并改编自stackoverflow 上的这个答案的 glob 代码,其他部分是从OpenBSD 的 fstat() 命令行界面借来的,最后,我使用 fts 遍历了文件系统,这要归功于这个可爱的答案的改编另一个stackoverflow问题:
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/mount.h>
#include <glob.h>
#include <kvm.h>
#include <fts.h>
struct fuser {
TAILQ_ENTRY(fuser) tq;
uid_t uid;
pid_t pid;
int flags;
};
struct filearg {
SLIST_ENTRY(filearg) next;
dev_t dev;
ino_t ino;
char *name;
TAILQ_HEAD(fuserhead, fuser) fusers;
};
SLIST_HEAD(fileargs, filearg);
static struct fileargs fileargs = SLIST_HEAD_INITIALIZER(fileargs);
static int fsflg = 0, cflg = 0, fuser = 0;
static bool match(struct filearg *fa, struct kinfo_file *kf) {
if (fa->dev == kf->va_fsid) {
if (fa->ino == kf->va_fileid) {
return true;
}
}
return false;
}
static int getfname(char *filename) {
static struct statfs *mntbuf = nullptr;
static int nmounts; int i = 0;
struct stat sb = { 0 };
struct filearg *cur = nullptr;
if (stat(filename, &sb)) {
return false;
}
if (fuser && !fsflg && S_ISBLK(sb.st_mode)) {
if (mntbuf == nullptr) {
nmounts = getmntinfo(&mntbuf, MNT_NOWAIT);
if (nmounts != -1) {
for (i = 0; i < nmounts; i++) {
if (!strcmp(mntbuf[i].f_mntfromname, filename)) {
if (stat(mntbuf[i].f_mntonname, &sb) == -1) {
return false;
}
cflg = 1;
break;
}
}
}
}
}
if (!fuser && S_ISSOCK(sb.st_mode)) {
char *newname = realpath(filename, nullptr);
if (newname != nullptr) filename = newname;
}
if ((cur = (struct filearg *)calloc(1, sizeof(*cur)))) {
if (!S_ISSOCK(sb.st_mode)) {
cur->ino = sb.st_ino;
cur->dev = sb.st_dev & 0xffff;
}
cur->name = filename;
TAILQ_INIT(&cur->fusers);
SLIST_INSERT_HEAD(&fileargs, cur, next);
return true;
}
return false;
}
static int compare(const FTSENT **one, const FTSENT **two) {
return (strcmp((*one)->fts_name, (*two)->fts_name));
}
static string find(struct kinfo_file *kif) {
FTS *file_system = nullptr;
FTSENT *child = nullptr;
FTSENT *parent = nullptr;
string result, path; glob_t glob_result;
memset(&glob_result, 0, sizeof(glob_result)); string pattern = "/*";
int return_value = glob(pattern.c_str(), GLOB_TILDE, NULL, &glob_result);
if (return_value) {
globfree(&glob_result);
}
vector<char *> vec; char **arr = nullptr;
for (size_t i = 0; i < glob_result.gl_pathc; i++) {
if (ngs::fs::directory_exists(glob_result.gl_pathv[i])) {
vec.push_back(glob_result.gl_pathv[i]);
}
}
if (vec.size()) {
arr = new char *[vec.size()]();
std::copy(vec.begin(), vec.end(), arr);
if (arr) {
file_system = fts_open(arr, FTS_COMFOLLOW | FTS_NOCHDIR, &compare);
if (file_system) {
while ((parent = fts_read(file_system)) && path.empty()) {
child = fts_children(file_system, 0);
while (child && child->fts_link) {
child = child->fts_link;
result = child->fts_path + string(child->fts_name);
struct filearg *fa = nullptr;
getfname((char *)result.c_str());
SLIST_FOREACH(fa, &fileargs, next) {
if (match(fa, kif)) {
path = fa->name;
break;
}
}
}
}
fts_close(file_system);
}
delete[] arr;
}
}
return path;
}
string file_bin_pathname(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, getpid(), sizeof(struct kinfo_file), &cntp))) {
for (int i = 0; i < cntp; i++) {
if (kif[i].fd_fd == fd) {
path = find(&kif[i]);
break;
}
}
kvm_close(kd);
}
return path;
}
同样重要的是要注意此代码有时(不一致地)返回完全错误的文件和一个与打开的文件描述符完全不匹配的文件,在我的测试中,这个错误在非常罕见和特定的情况下是可重现的,但这总比没有好,应该足以让您开始使用 OpenBSD。 与 macOS 一样,此解决方案随机获得一个硬链接(需要引用),以便在 macOS 和其他只能获得一个硬链接的平台上进行移植。 如果您不关心跨平台并且想要获取所有硬链接,则可以删除 while 循环中的中断并返回一个向量。 DragonFly BSD 与当前问题的 macOS 解决方案具有相同的解决方案(完全相同的代码) ,我可以验证这一点,因为我确实在 DragonFly BSD 上手动测试过。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.