Получение имени файла из файлового дескриптора в C

Можно ли получить имя файла дескриптора файла (Linux) в C?

8 ответов

Решение

Ты можешь использовать readlink на /proc/self/fd/NNN где NNN - дескриптор файла. Это даст вам имя файла таким, каким оно было при его открытии - однако, если файл был перемещен или удален с тех пор, он может перестать быть точным (хотя Linux может отслеживать переименования в некоторых случаях). Проверять, stat имя файла и fstat FD у вас есть, и убедитесь, 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) рекурсивно открывающиеся подкаталоги, пока не найдете в struct dirent таким образом получил тот же номер инода, который вы имели в оригинале struct stat (в это время, если вам нужен весь путь, а не только имя, вам придется пройтись по цепочке каталогов назад, чтобы восстановить ее).

Если этот общий подход приемлем, но вам нужен более подробный код на C, дайте нам знать, что его будет не сложно написать (хотя я бы не стал писать его, если он бесполезен, т.е. вы не можете противостоять неизбежно низкой производительности или возможность получения!= 1 результата для целей вашего приложения;-).

Прежде чем списать это как невозможное, я предлагаю вам взглянуть на исходный код команды lsof.

Могут быть ограничения, но, похоже, lsof способен определять дескриптор файла и имя файла. Эта информация существует в файловой системе /proc, поэтому ее можно получить из вашей программы.

Вы можете использовать fstat (), чтобы получить индекс файла с помощью struct stat. Затем, используя readdir (), вы можете сравнить найденные вами 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 хочет получить путь из файлового дескриптора, открыл любой процесс, вставив идентификатор процесса и не ограничиваясь только вызывающим, а также потенциально получая все жесткие ссылки и не ограничиваясь случайным , см. этот ответ. Это должно быть намного более производительно, чем обход всей вашей файловой системы, аналогично тому, как быстро это происходит в Linux и других решениях, которые более прямолинейны и точны. Пользователи FreeBSD могут получить то, что они ищут, в этом вопросе , потому что ошибка на уровне ОС, упомянутая в этом вопросе, с тех пор была устранена для более новых версий ОС.

Вот более общее решение, которое может получить только путь дескриптора файла, открытого вызывающим процессом, однако оно должно работать для большинства Unix-подобных готовых решений, со всеми теми же проблемами, что и предыдущее решение в отношении жестких ссылок и условий гонки, хотя работает немного быстрее из-за меньшего количества циклов if-then, for и т. д.:

      #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() вспомогательной функцией, которая хранит в основном любой эквивалент C для std::unordered_map, и соедините дескриптор файла с версией абсолютного пути того, что передается вашим оболочкам fopen()/open() (и эквиваленты только для Windows, которые не будут работать на UWP, такие как _wopen_s() и вся эта чепуха для поддержки UTF-8), что можно сделать с помощью realpath() в Unix-подобных системах или GetFullPathNameW() (*W для поддержки UTF-8) в Windows. realpath() будет разрешать символические ссылки (которые не так часто используются в Windows), а realpath() / GetFullPathNameW() преобразует ваш существующий файл, который вы открыли, из относительного пути, если он есть, в абсолютный путь. С дескриптором файла и абсолютным путем, хранящимся в C, эквивалентным std::unordered_map (который вам, вероятно, придется написать самостоятельно, используя массивы int и c-string с помощью malloc() и, в конечном итоге, free()), это будет опять же, быть быстрее, чем любое другое решение, которое выполняет динамический поиск в вашей файловой системе, но у него есть другое и непривлекательное ограничение, которое заключается в том, что оно не будет записывать файлы, которые были перемещены в вашей файловой системе, однако, по крайней мере, вы можете проверить, файл был удален с использованием вашего собственного кода для проверки существования, он также не будет обращать внимание на то, был ли файл заменен с момента его открытия и сохранения пути к дескриптору в памяти, что потенциально может привести к устаревшим результатам. Дайте мне знать, если вы хотите увидеть пример кода,

Другие вопросы по тегам