Es scheint mir, dass Linux es mit / proc / self / exe einfach hat. Ich möchte jedoch wissen, ob es eine bequeme Möglichkeit gibt, das Verzeichnis der aktuellen Anwendung in C / C ++ mit plattformübergreifenden Schnittstellen zu finden. Ich habe einige Projekte gesehen, die mit argv [0] herumgespielt haben, aber es scheint nicht ganz zuverlässig zu sein.
Wenn Sie jemals Mac OS X unterstützen müssten, das / proc / nicht hat, was hätten Sie getan? Verwenden Sie #ifdefs, um den plattformspezifischen Code zu isolieren (z. B. NSBundle). Oder versuchen Sie, den Pfad der ausführbaren Datei aus argv [0], $ PATH und so weiter abzuleiten, und riskieren Sie, in Randfällen Fehler zu finden?
ps -o comm
. Was mich hierher gebracht hat ist: "/proc/pid/path/a.out"Antworten:
Einige betriebssystemspezifische Schnittstellen:
_NSGetExecutablePath()
( Mann 3 Dyld )readlink /proc/self/exe
getexecname()
sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1
readlink /proc/curproc/file
(FreeBSD hat standardmäßig keine procfs)readlink /proc/curproc/exe
readlink /proc/curproc/file
GetModuleFileName()
mithModule
=NULL
Die tragbare (aber weniger zuverlässige) Methode ist zu verwenden
argv[0]
. Obwohl es vom aufrufenden Programm auf irgendetwas gesetzt werden kann, wird es konventionell entweder auf einen Pfadnamen der ausführbaren Datei oder auf einen Namen gesetzt, der mit gefunden wurde$PATH
.Einige Shells, einschließlich bash und ksh, setzen die Umgebungsvariable "
_
" auf den vollständigen Pfad der ausführbaren Datei, bevor sie ausgeführt wird. In diesem Fall können Sie es verwendengetenv("_")
, um es zu erhalten. Dies ist jedoch unzuverlässig, da dies nicht alle Shells tun und es auf irgendetwas gesetzt oder von einem übergeordneten Prozess übrig bleiben kann, der es vor dem Ausführen Ihres Programms nicht geändert hat.quelle
char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));
; das ist anders alsgetexecname()
- was das Äquivalent vonpargs -x <PID> | grep AT_SUN_EXECNAME
...Die Verwendung von
/proc/self/exe
ist nicht tragbar und unzuverlässig. Auf meinem Ubuntu 12.04-System müssen Sie root sein, um den Symlink lesen / folgen zu können. Dies führt dazu, dass das Boost-Beispiel und wahrscheinlich die veröffentlichtenwhereami()
Lösungen fehlschlagen.Dieser Beitrag ist sehr lang, behandelt jedoch die tatsächlichen Probleme und präsentiert Code, der zusammen mit der Validierung anhand einer Testsuite funktioniert.
Der beste Weg, um Ihr Programm zu finden, besteht darin, die gleichen Schritte zurückzuverfolgen, die das System verwendet. Dies erfolgt durch die Verwendung von
argv[0]
aufgelöst gegen Dateisystemstamm, pwd, Pfadumgebung und unter Berücksichtigung von Symlinks und der Kanonisierung von Pfadnamen. Dies ist aus dem Gedächtnis, aber ich habe dies in der Vergangenheit erfolgreich gemacht und es in einer Vielzahl von verschiedenen Situationen getestet. Es ist nicht garantiert, dass es funktioniert, aber wenn dies nicht der Fall ist, haben Sie wahrscheinlich viel größere Probleme und es ist insgesamt zuverlässiger als alle anderen besprochenen Methoden. Es gibt Situationen auf einem Unix-kompatiblen System, in denen eine ordnungsgemäße Handhabung erfolgtargv[0]
Sie gelangen nicht zu Ihrem Programm, aber dann werden Sie in einer nachweislich fehlerhaften Umgebung ausgeführt. Es ist auch ziemlich portabel für alle von Unix abgeleiteten Systeme seit etwa 1970 und sogar für einige nicht von Unix abgeleitete Systeme, da es im Wesentlichen auf der Standardfunktionalität von libc () und der Standardbefehlszeilenfunktionalität basiert. Es sollte unter Linux (alle Versionen), Android, Chrome OS, Minix, Original Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, funktionieren. Nächster Schritt usw. Und mit einer kleinen Änderung wahrscheinlich VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2 usw. Wenn ein Programm direkt von einer GUI-Umgebung aus gestartet wurde, sollte esargv[0]
einen absoluten Pfad festgelegt haben.Verstehen Sie, dass fast jede Shell auf jedem Unix-kompatiblen Betriebssystem, das jemals veröffentlicht wurde, Programme grundsätzlich auf dieselbe Weise findet und die Betriebsumgebung fast auf dieselbe Weise einrichtet (mit einigen optionalen Extras). Von jedem anderen Programm, das ein Programm startet, wird erwartet, dass es für dieses Programm dieselbe Umgebung (argv, Umgebungszeichenfolgen usw.) erstellt, als würde es von einer Shell ausgeführt, mit einigen optionalen Extras. Ein Programm oder Benutzer kann eine von dieser Konvention abweichende Umgebung für andere untergeordnete Programme einrichten, die es startet. Wenn dies jedoch der Fall ist, handelt es sich um einen Fehler, und das Programm hat keine vernünftige Erwartung, dass das untergeordnete Programm oder seine Untergebenen ordnungsgemäß funktionieren.
Mögliche Werte von
argv[0]
include:/path/to/executable
- absoluter Pfad../bin/executable
- relativ zu pwdbin/executable
- relativ zu pwd./foo
- relativ zu pwdexecutable
- Basisname, im Pfad findenbin//executable
- relativ zu pwd, nicht kanonischsrc/../bin/executable
- relativ zu pwd, nicht kanonisch, backtrackingbin/./echoargc
- relativ zu pwd, nicht kanonischWerte, die Sie nicht sehen sollten:
~/bin/executable
- neu geschrieben, bevor Ihr Programm ausgeführt wird.~user/bin/executable
- neu geschrieben, bevor Ihr Programm ausgeführt wirdalias
- neu geschrieben, bevor Ihr Programm ausgeführt wird$shellvariable
- neu geschrieben, bevor Ihr Programm ausgeführt wird*foo*
- Platzhalter, neu geschrieben, bevor Ihr Programm ausgeführt wird, nicht sehr nützlich?foo?
- Platzhalter, neu geschrieben, bevor Ihr Programm ausgeführt wird, nicht sehr nützlichDarüber hinaus können diese nicht-kanonische Pfadnamen und mehrere Ebenen symbolischer Verknüpfungen enthalten. In einigen Fällen kann es mehrere feste Links zu demselben Programm geben. Zum Beispiel
/bin/ls
,/bin/ps
,/bin/chmod
,/bin/rm
kann etc. harte Links sein/bin/busybox
.Führen Sie die folgenden Schritte aus, um sich selbst zu finden:
Speichern Sie pwd, PATH und argv [0] beim Eintritt in Ihr Programm (oder bei der Initialisierung Ihrer Bibliothek), da sie sich später ändern können.
Optional: Insbesondere bei Nicht-Unix-Systemen sollten Sie den Pfadnamen Host / Benutzer / Laufwerk-Präfix-Teil, falls vorhanden, trennen, aber nicht verwerfen. der Teil, der häufig vor einem Doppelpunkt steht oder einem anfänglichen "//" folgt.
Wenn
argv[0]
es sich um einen absoluten Pfad handelt, verwenden Sie diesen als Ausgangspunkt. Ein absoluter Pfad beginnt wahrscheinlich mit "/", aber auf einigen Nicht-Unix-Systemen beginnt er möglicherweise mit "\" oder einem Laufwerksbuchstaben oder einem Namenspräfix, gefolgt von einem Doppelpunkt.Andernfalls, wenn
argv[0]
es sich um einen relativen Pfad handelt (enthält "/" oder "\", beginnt aber nicht damit, z. B. "../../bin/foo", kombinieren Sie pwd + "/" + argv [0] (verwenden Sie) vorhandenes Arbeitsverzeichnis ab dem Zeitpunkt des Programmstarts, nicht aktuell).Andernfalls, wenn argv [0] ein einfacher Basisname ist (keine Schrägstriche), kombinieren Sie ihn nacheinander mit jedem Eintrag in der Umgebungsvariablen PATH und probieren Sie diese aus und verwenden Sie den ersten, der erfolgreich ist.
Optional: Else versucht , den sehr plattformspezifisch
/proc/self/exe
,/proc/curproc/file
(BSD), und(char *)getauxval(AT_EXECFN)
, und ,dlgetname(...)
falls vorhanden. Sie können diese zuvorargv[0]
basierten Methoden sogar ausprobieren , wenn sie verfügbar sind und keine Berechtigungsprobleme auftreten. In dem eher unwahrscheinlichen Fall (wenn Sie alle Versionen aller Systeme berücksichtigen), dass sie vorhanden sind und nicht ausfallen, sind sie möglicherweise maßgeblicher.Optional: Überprüfen Sie mithilfe eines Befehlszeilenparameters, ob ein Pfadname übergeben wurde.
Optional: Suchen Sie in der Umgebung nach einem Pfadnamen, der von Ihrem Wrapper-Skript explizit übergeben wurde, falls vorhanden.
Optional: Versuchen Sie als letzten Ausweg die Umgebungsvariable "_". Es könnte auf ein völlig anderes Programm verweisen, beispielsweise auf die Benutzer-Shell.
Lösen Sie Symlinks auf, es können mehrere Ebenen vorhanden sein. Es besteht die Möglichkeit von Endlosschleifen. Wenn diese vorhanden sind, wird Ihr Programm wahrscheinlich nicht aufgerufen.
Kanonisieren Sie den Dateinamen, indem Sie Teilzeichenfolgen wie "/foo/../bar/" in "/ bar /" auflösen. Beachten Sie, dass dies möglicherweise die Bedeutung ändert, wenn Sie einen Netzwerk-Mount-Punkt überschreiten. Daher ist die Kanonisierung nicht immer eine gute Sache. Auf einem Netzwerkserver kann ".." in symlink verwendet werden, um einen Pfad zu einer anderen Datei im Serverkontext anstatt auf dem Client zu durchlaufen. In diesem Fall möchten Sie wahrscheinlich den Client-Kontext, sodass die Kanonisierung in Ordnung ist. Konvertieren Sie auch Muster wie "/./" in "/" und "//" in "/".
readlink --canonicalize
Löst in der Shell mehrere Symlinks auf und kanonisiert den Namen. Chase kann ähnlich vorgehen, ist aber nicht installiert.realpath()
odercanonicalize_file_name()
, falls vorhanden, kann helfen.Wenn
realpath()
dies zur Kompilierungszeit nicht vorhanden ist, können Sie eine Kopie von einer zulässigen lizenzierten Bibliotheksdistribution ausleihen und in sich selbst kompilieren, anstatt das Rad neu zu erfinden. Korrigieren Sie den möglichen Pufferüberlauf (übergeben Sie die Größe des Ausgabepuffers, denken Sie an strncpy () vs strcpy ()), wenn Sie einen Puffer verwenden, der kleiner als PATH_MAX ist. Es ist möglicherweise einfacher, nur eine umbenannte private Kopie zu verwenden, als zu testen, ob sie vorhanden ist. Zulässige Lizenzkopie von android / darwin / bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.cBeachten Sie, dass mehrere Versuche erfolgreich oder teilweise erfolgreich sein können und möglicherweise nicht alle auf dieselbe ausführbare Datei verweisen. Überprüfen Sie daher Ihre ausführbare Datei. Möglicherweise haben Sie jedoch keine Leseberechtigung. Wenn Sie diese nicht lesen können, behandeln Sie dies nicht als Fehler. Oder überprüfen Sie etwas in der Nähe Ihrer ausführbaren Datei, z. B. das Verzeichnis "../lib/", das Sie suchen. Möglicherweise verfügen Sie über mehrere Versionen, gepackte und lokal kompilierte Versionen, lokale und Netzwerkversionen sowie tragbare Versionen mit lokalem und USB-Laufwerk usw., und es besteht eine geringe Wahrscheinlichkeit, dass Sie zwei inkompatible Ergebnisse mit unterschiedlichen Suchmethoden erhalten. Und "_" kann einfach auf das falsche Programm verweisen.
Ein Programm, das verwendet,
execve
kann absichtlich so eingestellt werdenargv[0]
, dass es nicht mit dem tatsächlichen Pfad kompatibel ist, der zum Laden des Programms verwendet wird, und PATH, "_", pwd usw. beschädigt, obwohl es im Allgemeinen nicht viele Gründe dafür gibt. Dies kann jedoch Auswirkungen auf die Sicherheit haben, wenn Sie anfälligen Code haben, der die Tatsache ignoriert, dass Ihre Ausführungsumgebung auf verschiedene Arten geändert werden kann, einschließlich, aber nicht beschränkt auf diese (Chroot, Fuse-Dateisystem, Hardlinks usw.). Dies ist möglich Damit Shell-Befehle PATH festlegen, aber nicht exportieren können.Sie müssen nicht unbedingt für Nicht-Unix-Systeme codieren, aber es wäre eine gute Idee, sich einiger Besonderheiten bewusst zu sein, damit Sie den Code so schreiben können, dass es für jemanden nicht so schwierig ist, ihn später zu portieren . Beachten Sie, dass einige Systeme (DEC VMS, DOS, URLs usw.) möglicherweise Laufwerksnamen oder andere Präfixe haben, die mit einem Doppelpunkt wie "C: \", "sys $ drive: [foo] bar" und "file" enden : /// foo / bar / baz ". Alte DEC VMS-Systeme verwenden "[" und "]", um den Verzeichnisabschnitt des Pfads einzuschließen, obwohl sich dies möglicherweise geändert hat, wenn Ihr Programm in einer POSIX-Umgebung kompiliert wurde. Einige Systeme, wie z. B. VMS, haben möglicherweise eine Dateiversion (am Ende durch ein Semikolon getrennt). Einige Systeme verwenden zwei aufeinanderfolgende Schrägstriche wie "// Laufwerk / Pfad / zu / Datei" oder "Benutzer @ Host: / Pfad / zu / Datei" (Befehl scp) oder "Datei: (durch Leerzeichen getrennt) und "PATH" durch Doppelpunkte getrennt, aber Ihr Programm sollte PATH erhalten, damit Sie sich keine Gedanken über den Pfad machen müssen. DOS und einige andere Systeme können relative Pfade haben, die mit einem Laufwerkpräfix beginnen. C: foo.exe bezieht sich auf foo.exe im aktuellen Verzeichnis auf Laufwerk C, daher müssen Sie das aktuelle Verzeichnis auf C: suchen und dieses für pwd verwenden. (durch Leerzeichen getrennt) und "PATH" durch Doppelpunkte getrennt, aber Ihr Programm sollte PATH erhalten, damit Sie sich keine Gedanken über den Pfad machen müssen. DOS und einige andere Systeme können relative Pfade haben, die mit einem Laufwerkpräfix beginnen. C: foo.exe bezieht sich auf foo.exe im aktuellen Verzeichnis auf Laufwerk C, daher müssen Sie das aktuelle Verzeichnis auf C: suchen und dieses für pwd verwenden.
Ein Beispiel für Symlinks und Wrapper auf meinem System:
Beachten Sie, dass Benutzer Rechnung geschrieben einen Link oben auf ein Programm bei HP, die die drei Grundfälle behandelt
argv[0]
. Es bedarf jedoch einiger Änderungen:strcat()
undstrcpy()
zu verwendenstrncat()
undstrncpy()
. Obwohl die Variablen mit der Länge PATHMAX deklariert sind, ist ein Eingabewert der Länge PATHMAX-1 plus der Länge der verketteten Zeichenfolgen> PATHMAX, und ein Eingabewert der Länge PATHMAX wäre nicht abgeschlossen.Wenn Sie also sowohl den HP-Code als auch den Realpath-Code kombinieren und beide so korrigieren, dass sie gegen Pufferüberläufe resistent sind, sollten Sie etwas haben, das richtig interpretiert werden kann
argv[0]
.Im Folgenden werden die tatsächlichen Werte
argv[0]
für verschiedene Möglichkeiten zum Aufrufen desselben Programms unter Ubuntu 12.04 dargestellt. Und ja, das Programm wurde versehentlich echoargc anstelle von echoargv genannt. Dies wurde mit einem Skript zum sauberen Kopieren durchgeführt, aber das manuelle Ausführen in der Shell führt zu denselben Ergebnissen (außer, dass Aliase im Skript nur funktionieren, wenn Sie sie explizit aktivieren).Diese Beispiele veranschaulichen, dass die in diesem Beitrag beschriebenen Techniken unter einer Vielzahl von Umständen funktionieren sollten und warum einige der Schritte erforderlich sind.
BEARBEITEN: Jetzt wurde das Programm, das argv [0] druckt, aktualisiert, um sich selbst zu finden.
Und hier ist die Ausgabe, die zeigt, dass sie sich in jedem der vorherigen Tests tatsächlich selbst gefunden hat.
Die beiden oben beschriebenen GUI-Starts finden das Programm ebenfalls korrekt.
Es gibt eine mögliche Gefahr. Die
access()
Funktion löscht Berechtigungen, wenn das Programm vor dem Testen eingestellt wurde. Wenn es eine Situation gibt, in der das Programm als erhöhter Benutzer, aber nicht als normaler Benutzer gefunden werden kann, kann es vorkommen, dass diese Tests fehlschlagen, obwohl es unwahrscheinlich ist, dass das Programm unter diesen Umständen tatsächlich ausgeführt wird. Man könnte stattdessen euidaccess () verwenden. Es ist jedoch möglich, dass ein unzugängliches Programm früher auf dem Pfad gefunden wird als der tatsächliche Benutzer.quelle
strncpy()
noch (besonders)strncat()
sicher im Code verwendet.strncpy()
garantiert keine Null-Kündigung; Wenn die Quellzeichenfolge länger als der Zielbereich ist, wird die Zeichenfolge nicht mit Null abgeschlossen.strncat()
ist sehr schwer zu bedienen;strncat(target, source, sizeof(target))
ist falsch (auch wenntarget
es sich zunächst um eine leere Zeichenfolge handelt), wenn siesource
länger als das Ziel ist. Die Länge ist die Anzahl der Zeichen, die ohne die nachfolgende Null sicher an das Ziel angehängt werden können, alsosizeof(target)-1
das Maximum.Schauen Sie sich die Whereami- Bibliothek von Gregory Pakosz an (die nur eine einzige C-Datei enthält). Auf diese Weise können Sie auf verschiedenen Plattformen den vollständigen Pfad zur aktuellen ausführbaren Datei abrufen. Derzeit ist es als Repo auf GitHub hier .
quelle
Eine Alternative unter Linux zur Verwendung von
/proc/self/exe
oderargv[0]
unter Verwendung der vom ELF-Interpreter übergebenen Informationen, die von glibc als solche bereitgestellt werden:Beachten Sie, dass
getauxval
es sich um eine glibc-Erweiterung handelt. Um robust zu sein, sollten Sie überprüfen, ob sie nicht zurückgegeben wirdNULL
(was darauf hinweist, dass der ELF-Interpreter denAT_EXECFN
Parameter nicht angegeben hat), aber ich denke nicht, dass dies unter Linux jemals ein Problem ist.quelle
Ja, das Isolieren von plattformspezifischem Code mit
#ifdefs
ist die herkömmliche Vorgehensweise.Ein anderer Ansatz wäre, einen sauberen
#ifdef
Header zu haben, der Funktionsdeklarationen enthält, und die Implementierungen in plattformspezifische Quelldateien abzulegen. Sehen Sie sich beispielsweise an, wie die Poco C ++ - Bibliothek für ihre Umgebungsklasse etwas Ähnliches tut .quelle
Damit dies plattformübergreifend zuverlässig funktioniert, müssen # ifdef-Anweisungen verwendet werden.
Der folgende Code findet den Pfad der ausführbaren Datei in Windows, Linux, MacOS, Solaris oder FreeBSD (obwohl FreeBSD nicht getestet wurde). Es verwendet boost > = 1.55.0, um den Code zu vereinfachen, aber es ist einfach genug, ihn zu entfernen, wenn Sie möchten. Verwenden Sie einfach Definitionen wie _MSC_VER und __linux, wie es das Betriebssystem und der Compiler erfordern.
Die obige Version gibt vollständige Pfade einschließlich des Namens der ausführbaren Datei zurück. Wenn Sie stattdessen den Pfad ohne den Namen der ausführbaren Datei möchten,
#include boost/filesystem.hpp>
ändern Sie die return-Anweisung in:quelle
Abhängig von der Version von QNX Neutrino gibt es verschiedene Möglichkeiten, den vollständigen Pfad und Namen der ausführbaren Datei zu ermitteln, mit der der laufende Prozess gestartet wurde. Ich bezeichne die Prozesskennung als
<PID>
. Versuche Folgendes:/proc/self/exefile
vorhanden ist, sind deren Inhalt die angeforderten Informationen./proc/<PID>/exefile
vorhanden ist, sind deren Inhalt die angeforderten Informationen./proc/self/as
vorhanden ist, dann:open()
die Datei.sizeof(procfs_debuginfo) + _POSIX_PATH_MAX
.devctl(fd, DCMD_PROC_MAPDEBUG_BASE,...
.procfs_debuginfo*
.path
Feld derprocfs_debuginfo
Struktur. Warnung : Aus irgendeinem Grund lässt QNX manchmal den ersten Schrägstrich/
des Dateipfads weg. Stellen Sie das/
bei Bedarf vor.3.
mit der Datei aus/proc/<PID>/as
.dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)
wodlinfo
sich eineDl_info
Struktur befindet, diedli_fname
möglicherweise die angeforderten Informationen enthält.Ich hoffe das hilft.
quelle
AFAIK, so nicht. Und es gibt auch eine Mehrdeutigkeit: Was möchten Sie als Antwort erhalten, wenn auf dieselbe ausführbare Datei mehrere Hardlinks "verweisen"? (Hardlinks "zeigen" nicht wirklich, sie sind dieselbe Datei, nur an einer anderen Stelle in der FS-Hierarchie.) Sobald execve () eine neue Binärdatei erfolgreich ausführt, gehen alle Informationen über ihre Argumente verloren.
quelle
Sie können argv [0] verwenden und die Umgebungsvariable PATH analysieren. Schauen Sie sich an: Ein Beispiel eines Programms, das sich selbst finden kann
quelle
execv
und kin den Pfad zur ausführbaren Datei separat vonargv
Portabilere Methode zum Abrufen des Pfadnamens des ausführbaren Images:
ps kann Ihnen den Pfad der ausführbaren Datei geben, vorausgesetzt, Sie haben die Prozess-ID. Außerdem ist ps ein POSIX-Dienstprogramm, daher sollte es portabel sein
Wenn die Prozess-ID 249297 lautet, gibt Ihnen dieser Befehl nur den Pfadnamen.
Erklärung der Argumente
-p - wählt einen bestimmten Prozess aus
-o comm - zeigt den Befehlsnamen an (-o cmd wählt die gesamte Befehlszeile aus)
--no-Heading - Zeigt keine Überschriftenzeile an, sondern nur die Ausgabe.
Das AC-Programm kann dies über Popen ausführen.
quelle
Wenn Sie C verwenden, können Sie die Funktion getwd verwenden:
Dadurch drucken Sie auf der Standardausgabe das aktuelle Verzeichnis der ausführbaren Datei.
quelle
Der Absolutwertpfad eines Programms befindet sich in der PWD der Umgebung Ihrer Hauptfunktion. Außerdem gibt es in C eine Funktion namens getenv, also gibt es diese.
quelle