Suchen des Pfads der aktuellen ausführbaren Datei ohne / proc / self / exe

190

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?

Uros Dimitrijevic
quelle
Ich googelte: hol meine ps -o comm. Was mich hierher gebracht hat ist: "/proc/pid/path/a.out"
Becken
Die Antwort von IMHO Prideout verdient es, ganz oben zu stehen, da sie die Anforderungen an "plattformübergreifende Schnittstellen" korrekt erfüllt und sehr einfach zu integrieren ist.
Stéphane Gourichon

Antworten:

348

Einige betriebssystemspezifische Schnittstellen:

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 verwenden getenv("_"), 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.

mark4o
quelle
3
Beachten Sie auch, dass _NSGetExecutablePath () keinen Symlinks folgt.
Naruse
1
NetBSD: readlink / proc / curproc / exe DragonFly BSD: readlink / proc / curproc / file
naruse
6
Solaris : char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));; das ist anders als getexecname()- was das Äquivalent von pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.
4
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)" Dies ist nicht der Pfad der ausführbaren Datei, sondern der Pfadname des Benutzerverzeichnisses, in dem Daten gespeichert werden sollen.
2
OpenBSD ist das einzige, wo Sie 2017 noch nicht können. Sie müssen den Weg PATH und argv [0] verwenden
Lothar
27

Die Verwendung von /proc/self/exeist 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öffentlichten whereami()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 es argv[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 pwd
  • bin/executable - relativ zu pwd
  • ./foo - relativ zu pwd
  • executable - Basisname, im Pfad finden
  • bin//executable - relativ zu pwd, nicht kanonisch
  • src/../bin/executable - relativ zu pwd, nicht kanonisch, backtracking
  • bin/./echoargc - relativ zu pwd, nicht kanonisch

Werte, 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 wird
  • alias - 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ützlich

Darü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/rmkann 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 zuvor argv[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 --canonicalizeLöst in der Shell mehrere Symlinks auf und kanonisiert den Namen. Chase kann ähnlich vorgehen, ist aber nicht installiert. realpath()oder canonicalize_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.c

Beachten 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, execvekann absichtlich so eingestellt werden argv[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:

/usr/bin/google-chrome is symlink to
/etc/alternatives/google-chrome  which is symlink to
/usr/bin/google-chrome-stable which is symlink to
/opt/google/chrome/google-chrome which is a bash script which runs
/opt/google/chome/chrome

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:

  • Es wird notwendig sein, alle neu zu schreiben strcat()und strcpy()zu verwenden strncat()und strncpy(). 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.
  • Es muss als Bibliotheksfunktion neu geschrieben werden und nicht nur zum Ausdrucken der Ergebnisse.
    • Namen können nicht kanonisiert werden (verwenden Sie den oben verlinkten Realpath-Code)
    • Symbolische Links können nicht aufgelöst werden (verwenden Sie den Realpath-Code).

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).

cat ~/src/echoargc.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
main(int argc, char **argv)
{
  printf("  argv[0]=\"%s\"\n", argv[0]);
  sleep(1);  /* in case run from desktop */
}
tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
echoargc
  argv[0]="echoargc"
bin/echoargc
  argv[0]="bin/echoargc"
bin//echoargc
  argv[0]="bin//echoargc"
bin/./echoargc
  argv[0]="bin/./echoargc"
src/../bin/echoargc
  argv[0]="src/../bin/echoargc"
cd ~/bin
*echo*
  argv[0]="echoargc"
e?hoargc
  argv[0]="echoargc"
./echoargc
  argv[0]="./echoargc"
cd ~/src
../bin/echoargc
  argv[0]="../bin/echoargc"
cd ~/junk
~/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
  argv[0]="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
  argv[0]="/home/whitis/bin/echoargc"
ln -s ~/bin/echoargc junk1
./junk1
  argv[0]="./junk1"
ln -s /home/whitis/bin/echoargc junk2
./junk2
  argv[0]="./junk2"
ln -s junk1 junk3
./junk3
  argv[0]="./junk3"


gnome-desktop-item-edit --create-new ~/Desktop
# interactive, create desktop link, then click on it
  argv[0]="/home/whitis/bin/echoargc"
# interactive, right click on gnome application menu, pick edit menus
# add menu item for echoargc, then run it from gnome menu
 argv[0]="/home/whitis/bin/echoargc"

 cat ./testargcscript 2>&1 | sed -e 's/^/    /g'
#!/bin/bash
# echoargc is in ~/bin/echoargc
# bin is in path
shopt -s expand_aliases
set -v
cat ~/src/echoargc.c
tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
echoargc
bin/echoargc
bin//echoargc
bin/./echoargc
src/../bin/echoargc
cd ~/bin
*echo*
e?hoargc
./echoargc
cd ~/src
../bin/echoargc
cd ~/junk
~/bin/echoargc
~whitis/bin/echoargc
alias echoit=~/bin/echoargc
echoit
echoarg=~/bin/echoargc
$echoarg
ln -s ~/bin/echoargc junk1
./junk1
ln -s /home/whitis/bin/echoargc junk2
./junk2
ln -s junk1 junk3
./junk3

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.

// Copyright 2015 by Mark Whitis.  License=MIT style
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

// "look deep into yourself, Clarice"  -- Hanibal Lector
char findyourself_save_pwd[PATH_MAX];
char findyourself_save_argv0[PATH_MAX];
char findyourself_save_path[PATH_MAX];
char findyourself_path_separator='/';
char findyourself_path_separator_as_string[2]="/";
char findyourself_path_list_separator[8]=":";  // could be ":; "
char findyourself_debug=0;

int findyourself_initialized=0;

void findyourself_init(char *argv0)
{

  getcwd(findyourself_save_pwd, sizeof(findyourself_save_pwd));

  strncpy(findyourself_save_argv0, argv0, sizeof(findyourself_save_argv0));
  findyourself_save_argv0[sizeof(findyourself_save_argv0)-1]=0;

  strncpy(findyourself_save_path, getenv("PATH"), sizeof(findyourself_save_path));
  findyourself_save_path[sizeof(findyourself_save_path)-1]=0;
  findyourself_initialized=1;
}


int find_yourself(char *result, size_t size_of_result)
{
  char newpath[PATH_MAX+256];
  char newpath2[PATH_MAX+256];

  assert(findyourself_initialized);
  result[0]=0;

  if(findyourself_save_argv0[0]==findyourself_path_separator) {
    if(findyourself_debug) printf("  absolute path\n");
     realpath(findyourself_save_argv0, newpath);
     if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
     if(!access(newpath, F_OK)) {
        strncpy(result, newpath, size_of_result);
        result[size_of_result-1]=0;
        return(0);
     } else {
    perror("access failed 1");
      }
  } else if( strchr(findyourself_save_argv0, findyourself_path_separator )) {
    if(findyourself_debug) printf("  relative path to pwd\n");
    strncpy(newpath2, findyourself_save_pwd, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    realpath(newpath2, newpath);
    if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
    if(!access(newpath, F_OK)) {
        strncpy(result, newpath, size_of_result);
        result[size_of_result-1]=0;
        return(0);
     } else {
    perror("access failed 2");
      }
  } else {
    if(findyourself_debug) printf("  searching $PATH\n");
    char *saveptr;
    char *pathitem;
    for(pathitem=strtok_r(findyourself_save_path, findyourself_path_list_separator,  &saveptr); pathitem; pathitem=strtok_r(NULL, findyourself_path_list_separator, &saveptr) ) {
       if(findyourself_debug>=2) printf("pathitem=\"%s\"\n", pathitem);
       strncpy(newpath2, pathitem, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       realpath(newpath2, newpath);
       if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
      if(!access(newpath, F_OK)) {
          strncpy(result, newpath, size_of_result);
          result[size_of_result-1]=0;
          return(0);
      } 
    } // end for
    perror("access failed 3");

  } // end else
  // if we get here, we have tried all three methods on argv[0] and still haven't succeeded.   Include fallback methods here.
  return(1);
}

main(int argc, char **argv)
{
  findyourself_init(argv[0]);

  char newpath[PATH_MAX];
  printf("  argv[0]=\"%s\"\n", argv[0]);
  realpath(argv[0], newpath);
  if(strcmp(argv[0],newpath)) { printf("  realpath=\"%s\"\n", newpath); }
  find_yourself(newpath, sizeof(newpath));
  if(1 || strcmp(argv[0],newpath)) { printf("  findyourself=\"%s\"\n", newpath); }
  sleep(1);  /* in case run from desktop */
}

Und hier ist die Ausgabe, die zeigt, dass sie sich in jedem der vorherigen Tests tatsächlich selbst gefunden hat.

tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
echoargc
  argv[0]="echoargc"
  realpath="/home/whitis/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin/echoargc
  argv[0]="bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin//echoargc
  argv[0]="bin//echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin/./echoargc
  argv[0]="bin/./echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
src/../bin/echoargc
  argv[0]="src/../bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/bin
*echo*
  argv[0]="echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
e?hoargc
  argv[0]="echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
./echoargc
  argv[0]="./echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/src
../bin/echoargc
  argv[0]="../bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/junk
~/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
rm junk1 junk2 junk3
ln -s ~/bin/echoargc junk1
./junk1
  argv[0]="./junk1"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
ln -s /home/whitis/bin/echoargc junk2
./junk2
  argv[0]="./junk2"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
ln -s junk1 junk3
./junk3
  argv[0]="./junk3"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"

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.

Whitis
quelle
1
Sie haben sich viel Mühe gegeben - gut gemacht. Leider wird weder 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 wenn targetes sich zunächst um eine leere Zeichenfolge handelt), wenn sie sourcelä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, also sizeof(target)-1das Maximum.
Jonathan Leffler
4
Der strncpy-Code ist korrekt, im Gegensatz zu der Methode, die ich verwenden soll. Ich schlage vor, Sie lesen den Code genauer. Es überläuft weder Puffer noch lässt es sie unberührt. Jede Verwendung von strncpy () / stncat () wird durch Kopieren der gültigen Größe von (buffer) begrenzt, und dann wird das letzte Zeichen des Puffers mit einer Null gefüllt, die das letzte Zeichen des Puffers überschreibt. strncat () verwendet den Parameter size jedoch fälschlicherweise als Zählung und kann überlaufen, da er vor Pufferüberlaufangriffen liegt.
Whitis
"sudo apt-get install libbsd0 libbsd-dev", dann s / strncat / strlcat /
whitis
1
Verwenden Sie PATH_MAX nicht. Dies hat vor 30 Jahren aufgehört zu funktionieren. Verwenden Sie immer malloc.
Lothar
Auch wenn Sie einen Init-Aufruf verwenden. Lösen Sie den Pfad zur Exe auf init und nicht nur auf einem Teil vollständig auf und führen Sie ihn später beim Aufruf aus. Hier ist keine verzögerte Auswertung möglich, wenn Sie im Resolver realpath verwenden. Zusammen mit den anderen Fehlern einfach der schlechteste Code, den ich in einer langen Antwort auf Stackoverflow gesehen habe.
Lothar
13

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 .

Stolz
quelle
8

Eine Alternative unter Linux zur Verwendung von /proc/self/exeoder argv[0]unter Verwendung der vom ELF-Interpreter übergebenen Informationen, die von glibc als solche bereitgestellt werden:

#include <stdio.h>
#include <sys/auxv.h>

int main(int argc, char **argv)
{
    printf("%s\n", (char *)getauxval(AT_EXECFN));
    return(0);
}

Beachten Sie, dass getauxvales sich um eine glibc-Erweiterung handelt. Um robust zu sein, sollten Sie überprüfen, ob sie nicht zurückgegeben wird NULL(was darauf hinweist, dass der ELF-Interpreter den AT_EXECFNParameter nicht angegeben hat), aber ich denke nicht, dass dies unter Linux jemals ein Problem ist.

Dolda2000
quelle
Ich mag das, da es einfach ist und glibc sowieso in Gtk + enthalten ist (was ich benutze).
Colin Keenan
4

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).

Ja, das Isolieren von plattformspezifischem Code mit #ifdefsist die herkömmliche Vorgehensweise.

Ein anderer Ansatz wäre, einen sauberen #ifdefHeader 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 .

StackedCrooked
quelle
4

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.

#include <string>
#include <boost/predef/os.h>

#if (BOOST_OS_WINDOWS)
#  include <stdlib.h>
#elif (BOOST_OS_SOLARIS)
#  include <stdlib.h>
#  include <limits.h>
#elif (BOOST_OS_LINUX)
#  include <unistd.h>
#  include <limits.h>
#elif (BOOST_OS_MACOS)
#  include <mach-o/dyld.h>
#elif (BOOST_OS_BSD_FREE)
#  include <sys/types.h>
#  include <sys/sysctl.h>
#endif

/*
 * Returns the full path to the currently running executable,
 * or an empty string in case of failure.
 */
std::string getExecutablePath() {
#if (BOOST_OS_WINDOWS)
    char *exePath;
    if (_get_pgmptr(&exePath) != 0)
        exePath = "";
#elif (BOOST_OS_SOLARIS)
    char exePath[PATH_MAX];
    if (realpath(getexecname(), exePath) == NULL)
        exePath[0] = '\0';
#elif (BOOST_OS_LINUX)
    char exePath[PATH_MAX];
    ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath));
    if (len == -1 || len == sizeof(exePath))
        len = 0;
    exePath[len] = '\0';
#elif (BOOST_OS_MACOS)
    char exePath[PATH_MAX];
    uint32_t len = sizeof(exePath);
    if (_NSGetExecutablePath(exePath, &len) != 0) {
        exePath[0] = '\0'; // buffer too small (!)
    } else {
        // resolve symlinks, ., .. if possible
        char *canonicalPath = realpath(exePath, NULL);
        if (canonicalPath != NULL) {
            strncpy(exePath,canonicalPath,len);
            free(canonicalPath);
        }
    }
#elif (BOOST_OS_BSD_FREE)
    char exePath[2048];
    int mib[4];  mib[0] = CTL_KERN;  mib[1] = KERN_PROC;  mib[2] = KERN_PROC_PATHNAME;  mib[3] = -1;
    size_t len = sizeof(exePath);
    if (sysctl(mib, 4, exePath, &len, NULL, 0) != 0)
        exePath[0] = '\0';
#endif
    return std::string(exePath);
}

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:

return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();
jtbr
quelle
@ Frank, nicht sicher, warum du das sagst. Funktioniert bei mir. Ich habe eine andere Antwort gesehen, die besagt, dass Sie root benötigen, um auf / proc / self / exe zuzugreifen, aber ich habe das auf keinem Linux-System gefunden, das ich ausprobiert habe (CentOS oder Mint).
jtbr
2

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:

  1. Wenn die Datei /proc/self/exefilevorhanden ist, sind deren Inhalt die angeforderten Informationen.
  2. Wenn die Datei /proc/<PID>/exefilevorhanden ist, sind deren Inhalt die angeforderten Informationen.
  3. Wenn die Datei /proc/self/asvorhanden ist, dann:
    1. open() die Datei.
    2. Weisen Sie mindestens einen Puffer zu sizeof(procfs_debuginfo) + _POSIX_PATH_MAX.
    3. Geben Sie diesen Puffer als Eingabe an devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Wirf den Puffer auf a procfs_debuginfo*.
    5. Die angeforderten Informationen befinden sich im pathFeld der procfs_debuginfoStruktur. Warnung : Aus irgendeinem Grund lässt QNX manchmal den ersten Schrägstrich /des Dateipfads weg. Stellen Sie das /bei Bedarf vor.
    6. Bereinigen (Datei schließen, Puffer freigeben usw.).
  4. Probieren Sie die Prozedur 3.mit der Datei aus /proc/<PID>/as.
  5. Versuchen Sie, dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)wo dlinfosich eine Dl_infoStruktur befindet, die dli_fnamemöglicherweise die angeforderten Informationen enthält.

Ich hoffe das hilft.

Dr. Koutheir Attouchi
quelle
1

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.

zvrba
quelle
1
"Sobald execve () eine neue Binärdatei erfolgreich ausführt, gehen alle Informationen zu ihren Argumenten verloren." Tatsächlich gehen die Argumente argp und envp nicht verloren, sie werden als argv [] und die Umgebung übergeben, und in einigen UN * Xes wird das Argument pathname oder etwas daraus konstruiertes entweder zusammen mit argp und envp (OS X) übergeben / iOS, Solaris) oder über einen der in der Antwort von mark4o aufgeführten Mechanismen verfügbar gemacht. Aber ja, das gibt Ihnen nur eine der harten Verbindungen, wenn es mehr als eine gibt.
1

Sie können argv [0] verwenden und die Umgebungsvariable PATH analysieren. Schauen Sie sich an: Ein Beispiel eines Programms, das sich selbst finden kann

Rechnung
quelle
7
Dies ist nicht wirklich zuverlässig (obwohl es im Allgemeinen mit Programmen funktioniert, die mit den üblichen Shells gestartet werden), da execvund kin den Pfad zur ausführbaren Datei separat vonargv
dmckee --- Ex-Moderator Kätzchen
9
Dies ist eine falsche Antwort. Es könnte Ihnen sagen , wo Sie könnten finden ein Programm mit dem gleichen Namen. Es sagt Ihnen jedoch nichts darüber aus, wo sich die aktuell ausgeführte ausführbare Datei tatsächlich befindet.
Larry Gritz
0

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.

    ps -p 24297 -o comm --no-heading

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.

MichaelMoser
quelle
Es gibt eine vollständige Startzeichenfolge mit Params.
ETech
- Keine Überschrift ist nicht tragbar
Gute Person
1
funktioniert nicht, wenn das erste Argument für execv kein absoluter Pfad ist.
Hroptatyr
-4

Wenn Sie C verwenden, können Sie die Funktion getwd verwenden:

int main()
{       
 char buf[4096];
 getwd(buf);
 printf(buf);
}

Dadurch drucken Sie auf der Standardausgabe das aktuelle Verzeichnis der ausführbaren Datei.

Lucas Ruelle
quelle
3
Zumindest unter Windows hat das aktuelle Arbeitsverzeichnis keine besondere Beziehung zur laufenden ausführbaren Datei. Beispielsweise kann CreateProcess eine EXE-Datei starten und ihr Arbeitsverzeichnis völlig unabhängig festlegen.
Spike0xff
Die Situation ist auf jedem anderen Betriebssystem dieselbe: Das aktuelle Verzeichnis ist manchmal zufällig das gleiche wie das ausführbare Verzeichnis, kann jedoch völlig anders sein.
Lassi
-10

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.

dacres
quelle