简体   繁体   中英

Retrieve filename from file descriptor in C

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

"

You can use readlink on /proc/self/fd/NNN where NNN is the file descriptor. This will give you the name of the file as it was when it was opened — however, if the file was moved or deleted since then, it may no longer be accurate (although Linux can track renames in some cases). To verify, stat the filename given and fstat the fd you have, and make sure st_dev and st_ino are the same.

Of course, not all file descriptors refer to files, and for those you'll see some odd text strings, such as pipe:[1538488] . Since all of the real filenames will be absolute paths, you can determine which these are easily enough. Further, as others have noted, files can have multiple hardlinks pointing to them - this will only report the one it was opened with. If you want to find all names for a given file, you'll just have to traverse the entire filesystem.

I had this problem on Mac OS X. We don't have a /proc virtual file system, so the accepted solution cannot work.

We do, instead, have a F_GETPATH command for fcntl :

 F_GETPATH          Get the path of the file descriptor Fildes.  The argu-
                    ment must be a buffer of size MAXPATHLEN or greater.

So to get the file associated to a file descriptor, you can use this snippet:

#include <sys/syslimits.h>
#include <fcntl.h>

char filePath[PATH_MAX];
if (fcntl(fd, F_GETPATH, filePath) != -1)
{
    // do something with the file path
}

Since I never remember where MAXPATHLEN is defined, I thought PATH_MAX from syslimits would be fine.

在Windows中,通过GetFileInformationByHandleEx传递FileNameInfo ,您可以检索文件名。

As Tyler points out, there's no way to do what you require "directly and reliably", since a given FD may correspond to 0 filenames (in various cases) or > 1 (multiple "hard links" is how the latter situation is generally described). If you do still need the functionality with all the limitations (on speed AND on the possibility of getting 0, 2, ... results rather than 1), here's how you can do it: first, fstat the FD -- this tells you, in the resulting struct stat , what device the file lives on, how many hard links it has, whether it's a special file, etc. This may already answer your question -- eg if 0 hard links you will KNOW there is in fact no corresponding filename on disk.

If the stats give you hope, then you have to "walk the tree" of directories on the relevant device until you find all the hard links (or just the first one, if you don't need more than one and any one will do). For that purpose, you use readdir (and opendir &c of course) recursively opening subdirectories until you find in a struct dirent thus received the same inode number you had in the original struct stat (at which time if you want the whole path, rather than just the name, you'll need to walk the chain of directories backwards to reconstruct it).

If this general approach is acceptable, but you need more detailed C code, let us know, it won't be hard to write (though I'd rather not write it if it's useless, ie you cannot withstand the inevitably slow performance or the possibility of getting != 1 result for the purposes of your application;-).

Before writing this off as impossible I suggest you look at the source code of the lsof command.

There may be restrictions but lsof seems capable of determining the file descriptor and file name. This information exists in the /proc filesystem so it should be possible to get at from your program.

You can use fstat() to get the file's inode by struct stat. Then, using readdir() you can compare the inode you found with those that exist (struct dirent) in a directory (assuming that you know the directory, otherwise you'll have to search the whole filesystem) and find the corresponding file name. Nasty?

Impossible. A file descriptor may have multiple names in the filesystem, or it may have no name at all.

Edit: Assuming you are talking about a plain old POSIX system, without any OS-specific APIs, since you didn't specify an OS.

After whole day of research and looking at OpenBSD's source code, I've come to find out there is no official API to do this on OpenBSD, though with convoluted workarounds, it is still possible with the following code, note this is pulled from one of my repositories and not all necessary headers are present, and you need to link with -lkvm , and also the static functions and global variables are mere helpers that are reused and the function you should actually call to get the filename is std::string file_bin_pathname(int fd) , the glob code I took and adapted from this answer on stackoverflow , other parts are borrowed from OpenBSD's fstat() command line interface , and lastly, I traversed the filesystem using fts thanks to an adaptation of this lovely answer on yet another stackoverflow question :

#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;
}

It's also important to note this code sometimes, (inconsistently), returns the completely wrong file and a file that doesn't match the opened file desciptor at all in any way, this bug is reproducible in very rare and specific cases in my testing, but this is better than nothing and should be more than enough to get you started on OpenBSD. Like macOS, this solution gets a hardlink at random (citation needed), for portability with macOS and other platforms that can only get one hard link. You could remove the break in the while loop and return a vector if you want don't care about being cross-platform and want to get all the hard links. DragonFly BSD has the same solution (the exact same code) as the macOS solution on the current question , which I can verify, as I did test it manually on DragonFly BSD.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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