繁体   English   中英

从C中的文件描述符中检索文件名

[英]Retrieve filename from file descriptor in C

是否可以在 C 中获取文件描述符(Linux)的文件名?

"

您可以在/proc/self/fd/NNN上使用readlink ,其中NNN是文件描述符。 这将为您提供文件打开时的名称-但是,如果此后文件被移动或删除,它可能不再准确(尽管Linux在某些情况下可以跟踪重命名)。 要进行验证,请stat给定的文件名和fstat所拥有的fd,并确保st_devst_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个结果;-)。

建议您先查看lsof命令的源代码,然后再将其写为不可能。

可能会有限制,但是lsof似乎能够确定文件描述符和文件名。 该信息存在于/ proc文件系统中,因此应该可以从您的程序中获取信息。

您可以使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM