web-dev-qa-db-fra.com

Trouver le chemin de l'exécutable en cours sans / proc / self / exe

Il me semble que Linux a la tâche facile avec/proc/self/exe. Mais j'aimerais savoir s'il existe un moyen pratique de trouver le répertoire de l'application actuelle en C/C++ avec des interfaces multiplates-formes. J'ai vu certains projets se disputer avec argv [0], mais cela ne semble pas totalement fiable.

Si vous deviez supporter, par exemple, Mac OS X, qui n'a pas/proc /, qu'auriez-vous fait? Utilisez #ifdefs pour isoler le code spécifique à la plate-forme (NSBundle, par exemple)? Ou essayez de déduire le chemin de l'exécutable à partir de argv [0], $ PATH et quoi, risquer de trouver des bogues dans les cas Edge?

179
Uros Dimitrijevic

Quelques interfaces spécifiques au système d'exploitation:

La méthode portable (mais moins fiable) consiste à utiliser argv[0]. Bien que le programme appelant puisse lui attribuer n'importe quoi, par convention, il est défini soit sur un chemin d'accès à l'exécutable, soit sur un nom trouvé à l'aide de $PATH.

Certains shells, notamment bash et ksh, définissez la variable d'environnement "_" sur le chemin complet de l'exécutable avant son exécution. Dans ce cas, vous pouvez utiliser getenv("_") pour l'obtenir. Cependant, ceci n'est pas fiable car tous les shells ne le font pas, et cela pourrait être réglé sur n'importe quoi ou être laissé par un processus parent qui ne l'a pas changé avant l'exécution de votre programme.

338
mark4o

L'utilisation de /proc/self/exe Est non portable et peu fiable. Sur mon système Ubuntu 12.04, vous devez être root pour lire/suivre le lien symbolique. Cela fera que l'exemple Boost et probablement les solutions whereami() postées échoueront.

Ce message est très long mais traite des problèmes actuels et présente un code qui fonctionne réellement avec la validation sur une suite de tests.

La meilleure façon de trouver votre programme consiste à retracer les mêmes étapes que celles utilisées par le système. Pour ce faire, utilisez argv[0] Résolu par rapport à la racine du système de fichiers, à un mot de passe, à l’environnement du chemin, aux liens symboliques et à la canonisation du nom du chemin. C’est de mémoire, mais j’ai réussi à le faire par le passé et j’ai pu le tester dans diverses situations. Son fonctionnement n’est pas garanti, mais si ce n’est pas le cas, vous avez probablement des problèmes beaucoup plus graves et il est globalement plus fiable que toutes les autres méthodes présentées. Il existe des situations sur un système compatible Unix dans lequel le traitement correct de argv[0] Ne vous mènera pas à votre programme mais que vous exécuterez dans un environnement défectueux. Il est également assez portable pour tous les systèmes dérivés d’Unix depuis environ 1970 et même pour certains systèmes dérivés d’Unix, car il repose essentiellement sur la fonctionnalité standard libc () et la fonctionnalité de ligne de commande standard. Il devrait fonctionner sous Linux (toutes versions), Android, Chrome OS, Minix, Unix original de Bell Labs, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, NeXTSTEP, etc. Et avec une petite modification probablement VMS, VM/CMS, DOS/Windows, ReactOS, OS/2, etc. Si un programme a été lancé directement à partir d'un environnement graphique, il doit ont défini argv[0] sur un chemin absolu.

Sachez que presque tous les Shell sur tous les systèmes d’exploitation compatibles Unix qui ont déjà été publiés trouvent essentiellement les programmes de la même manière et configurent l’environnement d’exploitation presque de la même manière (avec quelques extras en option). Et tout autre programme qui lance un programme doit créer le même environnement (argv, chaînes d'environnement, etc.) pour ce programme, comme s'il était exécuté à partir d'un shell, avec quelques extras facultatifs. Un programme ou un utilisateur peut configurer un environnement qui déroge à cette convention pour les autres programmes subordonnés qu'il lance, mais si c'est le cas, il s'agit d'un bogue et le programme n'a aucune attente raisonnable que le programme subordonné ou ses subordonnés fonctionneront correctement.

Les valeurs possibles de argv[0] Incluent:

  • /path/to/executable - chemin absolu
  • ../bin/executable - par rapport à pwd
  • bin/executable - par rapport à pwd
  • ./foo - par rapport à pwd
  • executable - nom de base, trouver dans le chemin
  • bin//executable - relatif à pwd, non canonique
  • src/../bin/executable - relatif à la pwd, non canonique, retour en arrière
  • bin/./echoargc - relatif à pwd, non canonique

Des valeurs que vous ne devriez pas voir:

  • ~/bin/executable - réécrit avant l'exécution de votre programme.
  • ~user/bin/executable - réécrit avant l'exécution de votre programme
  • alias - réécrit avant l'exécution de votre programme
  • $shellvariable - réécrit avant l'exécution de votre programme
  • *foo* - joker, réécrit avant l'exécution de votre programme, pas très utile
  • ?foo? - joker, réécrit avant l'exécution de votre programme, pas très utile

En outre, ils peuvent contenir des noms de chemins non canoniques et plusieurs couches de liens symboliques. Dans certains cas, il peut y avoir plusieurs liens en dur vers le même programme. Par exemple, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, Etc. peuvent être des liens physiques vers /bin/busybox.

Pour vous retrouver, suivez les étapes ci-dessous:

  • Enregistrez pwd, PATH et argv [0] à l'entrée de votre programme (ou à l'initialisation de votre bibliothèque) car ils pourraient changer ultérieurement.

  • Facultatif: en particulier pour les systèmes non-Unix, séparez, mais ne supprimez pas le nom de chemin d'accès, le préfixe hôte/utilisateur/lecteur, s'il est présent; la partie qui précède souvent les deux points ou suit un "//" initial.

  • Si argv[0] Est un chemin absolu, utilisez-le comme point de départ. Un chemin absolu commence probablement par "/", mais sur certains systèmes non Unix, il peut commencer par "\" ou par une lettre de lecteur ou un préfixe de nom suivi de deux points.

  • Sinon si argv[0] Est un chemin relatif (contient "/" ou "\" mais ne commence pas par celui-ci, tel que "../../bin/foo", alors combinez pwd + "/" + argv [0] (utilise le répertoire de travail actuel à partir du moment où le programme a démarré, et non à jour).

  • Sinon, si argv [0] est un nom de base simple (pas de barre oblique), combinez-le avec chaque entrée de la variable d'environnement PATH et essayez-les et utilisez la première qui réussit.

  • Facultatif: Sinon, essayez les très spécifiques plateformes /proc/self/exe, /proc/curproc/file (BSD), et (char *)getauxval(AT_EXECFN), Et dlgetname(...), le cas échéant. Vous pouvez même essayer ces méthodes avant argv[0], Si elles sont disponibles et si vous ne rencontrez pas de problèmes d'autorisation. Dans le cas peu probable (lorsque vous prenez en compte toutes les versions de tous les systèmes) qu'elles sont présentes et n'échouent pas, elles pourraient faire plus autorité.

  • Facultatif: recherchez un nom de chemin transmis à l'aide d'un paramètre de ligne de commande.

  • Facultatif: recherchez un chemin dans l'environnement explicitement transmis par votre script wrapper, le cas échéant.

  • Facultatif: En dernier recours, essayez la variable d'environnement "_". Cela peut indiquer un programme complètement différent, tel que les utilisateurs Shell.

  • Résolvez les liens symboliques, il peut y avoir plusieurs couches. Il existe la possibilité de boucles infinies, bien que si elles existent, votre programme ne sera probablement pas appelé.

  • Canonicalize filename en résolvant des sous-chaînes telles que "/foo/../bar/" en "/ bar /". Notez que cela peut potentiellement changer la signification si vous croisez un point de montage réseau. La canonisation n’est donc pas toujours une bonne chose. Sur un serveur de réseau, ".." dans symlink peut être utilisé pour parcourir un chemin vers un autre fichier dans le contexte du serveur plutôt que sur le client. Dans ce cas, vous voulez probablement le contexte du client afin que la canonisation soit correcte. Convertissez également des modèles tels que "/./" en "/" et "//" en "/". Dans Shell, readlink --canonicalize Résoudra plusieurs liens symboliques et canalisera le nom. Chase peut faire la même chose mais n'est pas installé. realpath() ou canonicalize_file_name(), si présent, peut aider.

Si realpath() n'existe pas à la compilation, vous pouvez en emprunter une copie à une distribution de bibliothèques sous licence permissive, et la compiler vous-même plutôt que de réinventer la roue. Corrigez le débordement potentiel de la mémoire tampon (passez dans la taille de la mémoire tampon de sortie, pensez à strncpy () vs strcpy ()) si vous utilisez une mémoire tampon inférieure à PATH_MAX. Il peut être plus simple d’utiliser une copie privée renommée plutôt que de vérifier si elle existe. Copie de licence permissive sous Android/darwin/bsd: https://Android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

Sachez que plusieurs tentatives peuvent être réussies ou partiellement et qu'elles peuvent ne pas toutes pointer vers le même exécutable, alors envisagez de vérifier votre exécutable; Cependant, vous n'avez peut-être pas l'autorisation de lecture. Si vous ne pouvez pas la lire, ne considérez pas cela comme un échec. Ou vérifiez quelque chose à proximité de votre exécutable tel que le répertoire "../lib/" que vous essayez de trouver. Vous pouvez disposer de plusieurs versions, de versions compilées et compilées localement, de versions locale et réseau, de versions locale et portable, etc. Il est peu probable que vous obteniez deux résultats incompatibles avec différentes méthodes de localisation. Et "_" peut simplement indiquer le mauvais programme.

Un programme utilisant execve peut délibérément paramétrer argv[0] De manière à ce qu'il ne soit pas compatible avec le chemin réel utilisé pour charger le programme et endommager les chemins PATH, "_", pwd, etc., même s'il n'y a généralement pas beaucoup de raisons faire cela; mais cela pourrait avoir des conséquences sur la sécurité si vous avez un code vulnérable qui ignore le fait que votre environnement d'exécution peut être modifié de différentes manières, y compris, mais sans s'y limiter, à celui-ci (chroot, système de fichiers Fuse, liens physiques, etc.). Il est possible pour que les commandes Shell définissent PATH mais ne parviennent pas à l'exporter.

Vous n'avez pas nécessairement besoin de coder pour les systèmes non-Unix, mais il serait judicieux de connaître certaines particularités pour pouvoir écrire le code de manière à ce qu'il ne soit pas aussi difficile pour quelqu'un de le porter plus tard. . Sachez que certains systèmes (DEC VMS, DOS, URL, etc.) peuvent avoir des noms de lecteur ou d’autres préfixes se terminant par un caractère deux-points, tels que "C: \", "sys $ drive: [foo] bar" et "fichier". : /// foo/bar/baz ". Les anciens systèmes DEC VMS utilisent "[" et "]" pour entourer la partie du chemin du répertoire, bien que cela puisse avoir changé si votre programme est compilé dans un environnement POSIX. Certains systèmes, tels que VMS, peuvent avoir une version de fichier (séparée par un point-virgule à la fin). Certains systèmes utilisent deux barres obliques consécutives comme dans "// lecteur/chemin/vers/fichier" ou "utilisateur @ hôte:/chemin/vers/fichier" (commande scp) ou "fichier: // nomhôte/chemin/vers/fichier" (URL). Dans certains cas (DOS, Windoze), PATH peut avoir différents caractères de séparation - ";" vs ":" et "\" vs "/" pour un séparateur de chemin. Dans csh/tsh, il y a "chemin" (délimité par des espaces) et "PATH" par deux points, mais votre programme doit recevoir PATH afin que vous n'ayez pas à vous soucier de ce chemin. DOS et certains autres systèmes peuvent avoir des chemins relatifs commençant par un préfixe de lecteur. C: foo.exe fait référence à foo.exe dans le répertoire en cours sur le lecteur C, vous devez donc rechercher le répertoire en cours sur C: et l’utiliser pour pwd.

Un exemple de liens symboliques et de wrappers sur mon système:

/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

Notez que l'utilisateur factureposté un lien ci-dessus vers un programme de HP qui gère les trois cas de base de argv[0]. Il a cependant besoin de quelques changements:

  • Il sera nécessaire de réécrire tous les strcat() et strcpy() pour utiliser strncat() et strncpy(). Même si les variables sont déclarées de longueur PATHMAX, une valeur d'entrée de longueur PATHMAX-1 plus la longueur de chaînes concaténées est> PATHMAX et une valeur d'entrée de longueur PATHMAX serait non terminée.
  • Elle doit être réécrite en tant que fonction de bibliothèque, plutôt que simplement pour imprimer les résultats.
    • Il ne parvient pas à canoniser les noms (utilisez le code de chemin réel auquel j'ai lié ci-dessus)
    • Il ne parvient pas à résoudre les liens symboliques (utilisez le code realpath)

Donc, si vous combinez le code HP et le code realpath et que vous corrigez les deux pour résister aux débordements de mémoire tampon, vous devriez avoir quelque chose qui peut interpréter correctement argv[0].

Ce qui suit illustre les valeurs réelles de argv[0] Pour différentes manières d'invoquer le même programme sur Ubuntu 12.04. Et oui, le programme a été appelé accidentellement echoargc au lieu de echoargv. Cela a été fait en utilisant un script pour une copie propre, mais le faire manuellement dans Shell donne les mêmes résultats (sauf que les alias ne fonctionnent pas dans le script, sauf si vous les activez explicitement).

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

Ces exemples illustrent le fait que les techniques décrites dans ce message devraient fonctionner dans un large éventail de circonstances et pourquoi certaines des étapes sont nécessaires.

EDIT: Le programme qui imprime argv [0] a été mis à jour pour se retrouver.

// 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 */
}

Et voici le résultat qui démontre que dans chacun des tests précédents, il s'est effectivement trouvé.

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"

Les deux lancements d’interface graphique décrits ci-dessus trouvent également le programme correctement.

Il y a un piège potentiel. La fonction access() supprime les autorisations si le programme est défini avant les tests. Si le programme peut être trouvé en tant qu'utilisateur élevé mais pas en tant qu'utilisateur normal, il peut arriver que ces tests échouent, même s'il est peu probable que le programme puisse être exécuté dans ces circonstances. On pourrait utiliser euidaccess () à la place. Il est toutefois possible qu’un programme inaccessible soit trouvé plus tôt sur le chemin que l’utilisateur réel.

20
whitis

Découvrez la bibliothèque whereami de Gregory Pakosz (qui ne contient qu'un seul fichier C); il vous permet d’obtenir le chemin complet de l’exécutable en cours sur diverses plates-formes. Actuellement, il est disponible sous forme de dépôt sur github ici .

11
prideout

Une alternative sous Linux à utiliser soit /proc/self/exe ou argv[0] utilise les informations transmises par l’interprète ELF, rendues disponibles par la glibc en tant que telles:

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

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

Notez que getauxval est une extension de la glibc, et pour être robuste, vous devez vérifier que le résultat n’est pas renvoyé NULL (ce qui indique que l’interpréteur ELF n’a pas fourni le fichier AT_EXECFN paramètre), mais je ne pense pas que ce soit réellement un problème sous Linux.

5
Dolda2000

Si vous deviez supporter, par exemple, Mac OS X, qui n'a pas/proc /, qu'auriez-vous fait? Utilisez #ifdefs pour isoler le code spécifique à la plate-forme (NSBundle, par exemple)?

Oui, isoler le code spécifique à la plate-forme avec #ifdefs est la méthode conventionnelle utilisée.

Une autre approche serait d’avoir un clean #ifdef _ moins en-tête qui contient les déclarations de fonction et place les implémentations dans des fichiers sources spécifiques à la plate-forme. Par exemple, voyez comment la bibliothèque Poco C++ fait quelque chose de similaire pour leur classe Environment .

3
StackedCrooked

Pour que cela fonctionne de manière fiable sur toutes les plateformes, vous devez utiliser les instructions #ifdef.

Le code ci-dessous trouve le chemin de l'exécutable sous Windows, Linux, MacOS, Solaris ou FreeBSD (bien que FreeBSD ne soit pas testé). Il utilise boost > = 1.55.0 pour simplifier le code, mais il est assez facile à supprimer si vous le souhaitez. Il suffit d'utiliser des définitions telles que _MSC_VER et __linux selon le système d'exploitation et le compilateur.

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

La version ci-dessus renvoie les chemins complets, y compris le nom de l'exécutable. Si au lieu de cela vous voulez le chemin sans le nom de l'exécutable, #include boost/filesystem.hpp> et remplacez l'instruction return par:

return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();
3
jtbr

Vous pouvez utiliser argv [0] et analyser la variable d’environnement PATH. Regardez: n exemple de programme qui peut se trouver

2
bill

Autant que je sache, rien de tel. Et il y a aussi une ambiguïté: que voudriez-vous obtenir comme réponse si le même exécutable a plusieurs liens durs "pointant" dessus? (Les liens physiques ne "pointent" pas en réalité, ils sont le même fichier, mais à un autre endroit de la hiérarchie FS.)]. Une fois que execve () a été exécuté avec succès un nouveau binaire, toutes les informations sur ses arguments sont perdues.

1
zvrba

Selon la version de QNX Neutrino, il existe différentes façons de trouver le chemin complet et le nom du fichier exécutable utilisé pour démarrer le processus en cours. Je note l'identifiant de processus comme <PID>. Essayez ce qui suit:

  1. Si le fichier /proc/self/exefile Existe, son contenu correspond aux informations demandées.
  2. Si le fichier /proc/<PID>/exefile Existe, son contenu correspond aux informations demandées.
  3. Si le fichier /proc/self/as Existe, alors:
    1. open() le fichier.
    2. Allouez un tampon d'au moins sizeof(procfs_debuginfo) + _POSIX_PATH_MAX.
    3. Donnez ce tampon en entrée à devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Cast le tampon à un procfs_debuginfo*.
    5. Les informations demandées se trouvent dans le champ path de la structure procfs_debuginfo. Warning: Pour une raison quelconque, QNX omet parfois la première barre oblique / Du chemin du fichier. Préférez que / Si nécessaire.
    6. Nettoyer (fermez le fichier, libérez le tampon, etc.).
  4. Essayez la procédure dans 3. Avec le fichier /proc/<PID>/as.
  5. Essayez dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)dlinfo est une structure Dl_info Dont dli_fname Peut contenir les informations demandées.

J'espère que ça aide.

1
Koutheir Attouchi

Un moyen plus portable d’obtenir le nom du chemin de l’image exécutable:

ps peut vous donner le chemin de l'exécutable, étant donné que vous avez l'identifiant du processus. Aussi, ps est un utilitaire POSIX, il devrait donc être portable.

par conséquent, si l'ID de processus est 249297, cette commande ne vous donne que le nom du chemin.

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

Explication des arguments

-p - sélectionne un processus donné

-o comm - affiche le nom de la commande (-o cmd sélectionne toute la ligne de commande)

--no-cap - n'affiche pas une ligne de titre, mais seulement la sortie.

Un programme C peut exécuter cela via popen.

0
MichaelMoser