Ist es möglich, den Dateinamen eines Dateideskriptors (Linux) in C zu ermitteln?
Antworten
Zu viele Anzeigen?Sie können fstat() verwenden, um die Inode der Datei mit struct stat zu ermitteln. Dann können Sie mit readdir() den gefundenen Inode mit denen vergleichen, die in einem Verzeichnis existieren (struct dirent) (vorausgesetzt, Sie kennen das Verzeichnis, sonst müssen Sie das gesamte Dateisystem durchsuchen) und den entsprechenden Dateinamen finden. Unangenehm?
Es gibt keine offizielle API, um dies unter OpenBSD zu tun, obwohl es mit einigen sehr verworrenen Workarounds immer noch mit dem folgenden Code möglich ist, beachten Sie, dass Sie mit -lkvm
y -lc
. Der Code, der FTS zum Durchlaufen des Dateisystems verwendet, stammt aus diese Antwort .
#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;
}
Wenn die Funktion die Datei nicht finden kann (z. B. weil sie nicht mehr existiert), gibt sie eine leere Zeichenfolge zurück. Wenn die Datei verschoben wurde, wird meiner Erfahrung nach beim Verschieben der Datei in den Papierkorb stattdessen der neue Speicherort der Datei zurückgegeben, sofern dieser nicht bereits von FTS durchsucht wurde. Bei Dateisystemen mit mehreren Dateien wird es langsamer sein.
Je tiefer die Suche in den Verzeichnisbaum des gesamten Dateisystems geht, ohne die Datei zu finden, desto wahrscheinlicher ist es, dass es zu einer Wettlaufsituation kommt, auch wenn dies aufgrund der hohen Performance immer noch sehr unwahrscheinlich ist. Ich bin mir bewusst, dass meine OpenBSD-Lösung in C++ und nicht in C ist. Du kannst sie gerne in C ändern, und der größte Teil der Codelogik wird der gleiche sein. Wenn ich Zeit habe, werde ich versuchen, es in C umzuschreiben, hoffentlich bald. Wie macOS bekommt diese Lösung einen Hardlink nach dem Zufallsprinzip (Zitat erforderlich), für die Portabilität mit Windows und anderen Plattformen, die nur einen Hardlink bekommen können. Man könnte die Unterbrechung in der while-Schleife entfernen und einen Vektor zurückgeben, wenn man sich nicht darum schert, plattformübergreifend zu sein und alle Hardlinks erhalten möchte. DragonFly BSD und NetBSD haben die gleiche Lösung (den exakt gleichen Code) wie die macOS-Lösung zur aktuellen Frage die ich manuell überprüft habe. Wenn ein macOS-Benutzer einen Pfad aus einem Dateideskriptor erhalten möchte, der von einem beliebigen Prozess geöffnet wurde, indem er eine Prozess-ID eingibt, und nicht nur auf den aufrufenden Prozess beschränkt sein möchte, während er möglicherweise auch alle Hardlinks erhält und nicht auf einen zufälligen beschränkt ist, siehe diese Antwort . Es sollte viel leistungsfähiger sein als das Durchlaufen des gesamten Dateisystems, ähnlich wie unter Linux und anderen Lösungen, die unkomplizierter und zielgerichteter sind. FreeBSD-Benutzer können in dieser Frage das finden, was sie suchen denn der in dieser Frage erwähnte Fehler auf Betriebssystemebene wurde inzwischen für neuere Betriebssystemversionen behoben.
Hier ist eine allgemeinere Lösung, die nur den Pfad eines Dateideskriptors abrufen kann, der vom aufrufenden Prozess geöffnet wurde. Sie sollte jedoch bei den meisten Unix-ähnlichen Systemen sofort funktionieren und hat dieselben Probleme wie die frühere Lösung in Bezug auf harte Links und Race Conditions, ist jedoch etwas schneller, da sie weniger if-then, for-Schleifen usw. enthält:
#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;
}
Eine noch schnellere Lösung, die auch auf den aufrufenden Prozess beschränkt ist, aber etwas performanter sein sollte, ist, alle Aufrufe von fopen() und open() mit einer Hilfsfunktion zu verpacken, die im Grunde das C-Äquivalent einer std:: unordered_map speichert, und den Dateideskriptor mit der absoluten Pfadversion dessen, was an Ihre fopen()/open()-Wrapper (und die reinen Windows-Äquivalente, die unter UWP nicht funktionieren, wie _wopen_s() und all der Unsinn zur Unterstützung von UTF-8) übergeben wird, paaren, was mit realpath() auf Unix-ähnlichen Systemen oder GetFullPathNameW() (*W für UTF-8-Unterstützung) auf Windows möglich ist. realpath() löst symbolische Links auf (die unter Windows nicht so häufig verwendet werden), und realpath() / GetFullPathNameW() konvertiert die von Ihnen geöffnete Datei von einem relativen Pfad in einen absoluten Pfad, falls es einer ist. Mit dem Dateideskriptor und dem absoluten Pfad, die in einem C-Äquivalent zu einer std:: unordered_map (die Sie wahrscheinlich selbst schreiben müssen, indem Sie malloc()'d und eventuell free()'d int- und c-string-Arrays verwenden), wird dies wiederum schneller sein als jede andere Lösung, die eine dynamische Suche in Ihrem Dateisystem durchführt, aber es hat eine andere und unangenehme Einschränkung, nämlich dass es keine Notiz von Dateien macht, die in Ihrem Dateisystem verschoben wurden, Allerdings können Sie zumindest überprüfen, ob die Datei gelöscht wurde, indem Sie Ihren eigenen Code verwenden, um die Existenz zu testen. Außerdem wird nicht vermerkt, ob die Datei seit dem Zeitpunkt, an dem Sie sie geöffnet und den Pfad zum Deskriptor im Speicher abgelegt haben, ersetzt wurde, wodurch Sie möglicherweise veraltete Ergebnisse erhalten. Lassen Sie es mich wissen, wenn Sie ein Codebeispiel dafür sehen möchten, obwohl ich diese Lösung wegen der wechselnden Speicherorte der Dateien nicht empfehle.
- See previous answers
- Weitere Antworten anzeigen