Recherche du chemin de l'exécutable actuel sans / proc / self / exe

190

Il me semble que Linux est 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 multiplateformes. J'ai vu certains projets se dérober avec argv [0], mais cela ne semble pas entièrement fiable.

Si jamais vous aviez à prendre en charge, 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 autres, risquant de trouver des bogues dans les cas extrêmes?

Uros Dimitrijevic
la source
Dupliquer: stackoverflow.com/questions/933850/…
Greg Hewgill
J'ai googlé: récupérez mon ps -o comm. Ce qui m'a amené ici est: "/proc/pid/path/a.out"
bassin
La réponse de IMHO prideout mérite d'être au top, car elle répond correctement à l'exigence «d'interfaces multiplateformes» et est très facile à intégrer.
Stéphane Gourichon le

Réponses:

348

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

La méthode portable (mais moins fiable) est à utiliser argv[0]. Bien qu'il puisse être défini sur n'importe quoi par le programme appelant, par convention, il est défini sur un nom de chemin de l'exécutable ou sur un nom trouvé à l'aide de $PATH.

Certains shells, y compris bash et ksh, définissent 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, cela n'est pas fiable car tous les shells ne le font pas, et il peut être défini sur n'importe quoi ou être laissé par un processus parent qui ne l'a pas modifié avant d'exécuter votre programme.

mark4o
la source
3
Et notez également que _NSGetExecutablePath () ne suit pas les liens symboliques.
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));; c'est différent de getexecname()- ce qui équivaut à pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.
4
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)" Ce n'est pas le chemin de l'exécutable, c'est le chemin du répertoire par utilisateur où les données doivent être stockées.
2
OpenBSD est le seul où vous ne pouvez toujours pas en 2017. Vous devez utiliser le chemin PATH et argv [0]
Lothar
27

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

Cet article est très long mais traite des problèmes réels et présente du code qui fonctionne réellement avec la validation par rapport à une suite de tests.

La meilleure façon de trouver votre programme est de retracer les mêmes étapes que le système utilise. Ceci est fait en utilisant argv[0]résolu contre la racine du système de fichiers, pwd, l'environnement de chemin et en tenant compte des liens symboliques et de la canonisation des noms de chemin. C'est de mémoire, mais je l'ai fait dans le passé avec succès et l'ai testé dans une variété de situations différentes. Il n'est pas garanti que cela fonctionne, mais si ce n'est pas le cas, vous avez probablement des problèmes beaucoup plus importants et il est globalement plus fiable que toutes les autres méthodes discutées. Il existe des situations sur un système compatible Unix dans lesquelles une bonne gestion desargv[0]ne vous amènera pas à votre programme, mais vous exécutez alors dans un environnement cassé de manière certifiée. Il est également assez portable pour tous les systèmes dérivés d'Unix depuis environ 1970 et même certains systèmes non dérivés d'Unix car il repose essentiellement sur la fonctionnalité standard de libc () et la fonctionnalité de ligne de commande standard. Il devrait fonctionner sur Linux (toutes versions), Android, Chrome OS, Minix, Bell Labs Unix, 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 depuis un environnement GUI, il aurait dû être défini argv[0]sur un chemin absolu.

Comprenez que presque tous les shell sur tous les systèmes d'exploitation compatibles Unix qui ont jamais été publiés trouvent fondamentalement les programmes de la même manière et configure l'environnement d'exploitation presque de la même manière (avec quelques extras optionnels). Et tout autre programme qui lance un programme est censé 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 optionnels. Un programme ou un utilisateur peut configurer un environnement qui s'écarte de cette convention pour d'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 fonctionnent 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 - par rapport à pwd, non canonique
  • src/../bin/executable - par rapport à pwd, non canonique, retour arrière
  • bin/./echoargc - par rapport à pwd, non canonique

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* - wildcard, réécrit avant l'exécution de votre programme, pas très utile
  • ?foo? - wildcard, réécrit avant l'exécution de votre programme, pas très utile

En outre, ceux-ci peuvent contenir des noms de chemin non canoniques et plusieurs couches de liens symboliques. Dans certains cas, il peut y avoir plusieurs liens physiques vers le même programme. Par exemple, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, etc. , peuvent être des liens durs 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 peuvent changer plus tard.

  • Facultatif: en particulier pour les systèmes non Unix, séparez mais ne supprimez pas la partie du préfixe hôte / utilisateur / lecteur du chemin, si elle est présente; la partie qui précède souvent un 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 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 lui, comme "../../bin/foo", alors combinez pwd + "/" + argv [0] (utilisez répertoire de travail actuel à partir du démarrage du programme, pas en cours).

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

  • Facultatif: Sinon, essayez le très spécifique à la plate-forme /proc/self/exe, /proc/curproc/file(BSD), et (char *)getauxval(AT_EXECFN), et le dlgetname(...)cas échéant. Vous pouvez même essayer ces argv[0]méthodes antérieures , si elles sont disponibles et si vous ne rencontrez pas de problèmes d'autorisation. Dans le cas quelque peu improbable (lorsque vous considérez toutes les versions de tous les systèmes) où elles sont présentes et n'échouent pas, elles peuvent faire plus autorité.

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

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

  • Facultatif: en dernier recours, essayez la variable d'environnement "_". Il peut pointer vers un programme complètement différent, tel que le shell des utilisateurs.

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

  • Canonicalisez le nom de fichier en résolvant les sous-chaînes telles que "/foo/../bar/" en "/ bar /". Notez que cela peut potentiellement changer la signification si vous traversez un point de montage réseau, donc la canonisation n'est pas toujours une bonne chose. Sur un serveur réseau, ".." dans le lien symbolique 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 client, donc la canonisation est correcte. Convertissez également des motifs tels que "/./" en "/" et "//" en "/". Dans le shell, readlink --canonicalizerésoudra plusieurs liens symboliques et canonisera le nom. Chase peut faire la même chose mais n'est pas installé. realpath()ou canonicalize_file_name(), si présent, peut aider.

S'il realpath()n'existe pas au moment de la compilation, vous pouvez emprunter une copie d'une distribution de bibliothèque 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 à la taille du tampon de sortie, pensez à strncpy () vs strcpy ()) si vous utilisez un tampon inférieur à PATH_MAX. Il peut être plus facile d'utiliser simplement une copie privée renommée plutôt que de tester si elle existe. Copie de licence permissive depuis 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 réussies et qu'elles peuvent ne pas toutes pointer vers le même exécutable, pensez donc à vérifier votre exécutable; cependant, vous n'avez peut-être pas l'autorisation de lecture - si vous ne pouvez pas le lire, ne le considérez pas 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 avoir plusieurs versions, des versions packagées et compilées localement, des versions locales et réseau, et des versions portables locales et USB, etc. et il y a une petite possibilité que vous obteniez deux résultats incompatibles à partir de différentes méthodes de localisation. Et "_" peut simplement pointer vers le mauvais programme.

Un programme utilisant execvepeut délibérément être défini argv[0]pour être incompatible avec le chemin d'accès réel utilisé pour charger le programme et corrompre PATH, "_", pwd, etc. bien qu'il n'y ait généralement pas beaucoup de raisons de le faire; mais cela pourrait avoir des implications 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 diverses manières, y compris, mais sans s'y limiter, celle-ci (chroot, système de fichiers fusionné, liens physiques, etc.). 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 ce serait une bonne idée d'être conscient de certaines des particularités afin que vous puissiez écrire le code de telle manière qu'il ne soit pas aussi difficile pour quelqu'un de porter plus tard . Sachez que certains systèmes (DEC VMS, DOS, URL, etc.) peuvent avoir des noms de lecteur ou d'autres préfixes qui se terminent par un signe deux-points tels que "C: \", "sys $ lecteur: [foo] bar", et "fichier : /// foo / bar / baz ". Les anciens systèmes DEC VMS utilisent "[" et "]" pour entourer la partie répertoire du chemin, 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: (délimité par des espaces) et "PATH" délimité par des deux-points mais votre programme devrait recevoir PATH afin que vous n'ayez pas à vous soucier du 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 actuel sur le lecteur C, vous devez donc rechercher le répertoire actuel sur C: et l'utiliser pour pwd. (délimité par des espaces) et "PATH" délimité par des deux-points mais votre programme devrait recevoir PATH afin que vous n'ayez pas à vous soucier du 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 actuel sur le lecteur C, vous devez donc rechercher le répertoire actuel 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 la facture d' utilisateur a publié un lien ci-dessus vers un programme chez HP qui gère les trois cas de base de argv[0]. Il a cependant besoin de quelques changements:

  • Il faudra réécrire tous les strcat()et strcpy()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 des chaînes concaténées est> PATHMAX et une valeur d'entrée de longueur PATHMAX ne serait pas terminée.
  • Il doit être réécrit comme une fonction de bibliothèque, plutôt que simplement pour imprimer les résultats.
    • Il ne parvient pas à canoniser les noms (utilisez le code realpath auquel j'ai lié ci-dessus)
    • Il ne parvient pas à résoudre les liens symboliques (utilisez le code realpath)

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

Ce qui suit illustre les valeurs réelles de argv[0]différentes manières d'appeler le même programme sur Ubuntu 12.04. Et oui, le programme a été accidentellement nommé echoargc au lieu de echoargv. Cela a été fait en utilisant un script pour une copie propre, mais le faire manuellement dans le shell donne les mêmes résultats (sauf que les alias ne fonctionnent pas dans le script à moins que vous ne les activiez 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 que les techniques décrites dans cet article devraient fonctionner dans un large éventail de circonstances et pourquoi certaines étapes sont nécessaires.

EDIT: Maintenant, le programme qui imprime argv [0] a été mis à jour pour se trouver réellement.

// 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 réellement retrouvé.

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 GUI décrits ci-dessus trouvent également correctement le programme.

Il y a un écueil potentiel. La access()fonction supprime les autorisations si le programme est défini avant le test. S'il y a une situation où le programme peut être trouvé en tant qu'utilisateur élevé mais pas en tant qu'utilisateur régulier, alors il peut y avoir une situation où ces tests échoueraient, bien qu'il soit peu probable que le programme puisse réellement être exécuté dans ces circonstances. On pourrait utiliser euidaccess () à la place. Il est possible, cependant, qu'il puisse trouver un programme inaccessible plus tôt sur le chemin que l'utilisateur réel ne le pourrait.

whitis
la source
1
Vous y consacrez beaucoup d'efforts - bravo. Malheureusement, ni strncpy()ni (surtout) strncat()n'est utilisé en toute sécurité dans le code. strncpy()ne garantit pas la résiliation nulle; si la chaîne source est plus longue que l'espace cible, la chaîne n'est pas terminée par null. strncat()est très difficile à utiliser; strncat(target, source, sizeof(target))est faux (même si targetc'est une chaîne vide pour commencer) si sourceest plus long que la cible. La longueur est le nombre de caractères qui peuvent être ajoutés en toute sécurité à la cible en excluant la valeur null de fin, il en sizeof(target)-1est de même pour le maximum.
Jonathan Leffler
4
Le code strncpy est correct, contrairement à la méthode que vous suggérez que je devrais utiliser. Je vous suggère de lire le code plus attentivement. Il ne déborde pas des tampons ni ne les laisse intacts. Chaque utilisation de strncpy () / stncat () est limitée par la copie de sizeof (buffer), qui est valide, puis le dernier caractère du buffer est rempli d'un zéro écrasant le dernier caractère du buffer. strncat (), cependant, n'utilise pas correctement le paramètre size comme décompte et peut déborder du fait qu'il est antérieur aux attaques par débordement de tampon.
whitis
"sudo apt-get install libbsd0 libbsd-dev", puis s / strncat / strlcat /
whitis
1
N'utilisez pas PATH_MAX. Cela a cessé de fonctionner il y a 30 ans, utilisez toujours malloc.
Lothar
Aussi si vous utilisez un appel init. Résolvez entièrement le chemin vers l'exe sur init et pas seulement une partie, puis faites-le plus tard sur appel. Aucune évaluation paresseuse n'est possible ici si vous utilisez realpath dans le résolveur. Avec les autres erreurs, tout simplement le pire code que j'ai vu sur stackoverflow dans une longue réponse.
Lothar
13

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 actuel sur une variété de plates-formes. Actuellement, il est disponible sous forme de dépôt sur github ici .

fierté
la source
8

Une alternative sous Linux à l'utilisation de l'un /proc/self/exeou l' autre ou argv[0]utilise les informations transmises par l'interpréteur ELF, mises à disposition 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 s'agit d'une extension glibc, et pour être robuste, vous devez vérifier qu'elle ne retourne pas NULL(indiquant que l'interpréteur ELF n'a pas fourni le AT_EXECFNparamètre), mais je ne pense pas que ce soit réellement un problème sous Linux.

Dolda2000
la source
J'aime ça car c'est simple et la glibc est de toute façon incluse avec Gtk + (que j'utilise).
Colin Keenan
4

Si jamais vous aviez à prendre en charge, 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 #ifdefsest la manière conventionnelle de procéder.

Une autre approche consisterait à avoir un en- #ifdeftête clean -less contenant des déclarations de fonction et à placer les implémentations dans des fichiers source spécifiques à la plate-forme. Par exemple, découvrez comment la bibliothèque Poco C ++ fait quelque chose de similaire pour sa classe Environment .

EmpiléCrooked
la source
4

Pour que cela fonctionne de manière fiable sur toutes les plates-formes, il faut utiliser des 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. Utilisez simplement des définitions comme _MSC_VER et __linux selon les besoins du système d'exploitation et du 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 des chemins complets, y compris le nom de l'exécutable. Si à la place vous voulez le chemin sans le nom de l'exécutable, #include boost/filesystem.hpp>changez l'instruction return en:

return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();
jtbr
la source
@Frank, je ne sais pas pourquoi vous dites ça. Travaille pour moi. J'ai vu une autre réponse affirmer que vous avez besoin de root pour accéder à / proc / self / exe, mais je n'ai trouvé cela sur aucun système Linux que j'ai essayé (CentOS ou Mint).
jtbr
2

Selon la version de QNX Neutrino , il existe différentes manières de trouver le chemin complet et le nom du fichier exécutable utilisé pour démarrer le processus en cours. Je désigne l'identifiant du processus par <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. Allouer un tampon d'au moins sizeof(procfs_debuginfo) + _POSIX_PATH_MAX .
    3. Donnez ce tampon comme entrée à devctl(fd, DCMD_PROC_MAPDEBUG_BASE,... .
    4. Cast le tampon en un procfs_debuginfo* .
    5. Les informations demandées se trouvent dans le pathchamp de la procfs_debuginfostructure. Avertissement : pour une raison quelconque, QNX omet parfois la première barre oblique /du chemin du fichier. Préparez cela /si nécessaire.
    6. Nettoyez (fermez le fichier, libérez le tampon, etc.).
  4. Essayez la procédure 3.avec le fichier/proc/<PID>/as .
  5. Essayez dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)où se dlinfotrouve une Dl_infostructure qui dli_fnamepourrait contenir les informations demandées.

J'espère que ça aide.

Dr Koutheir Attouchi
la source
1

AFAIK, pas une telle manière. Et il y a aussi une ambuiguïté: que souhaiteriez-vous obtenir comme réponse si le même exécutable a plusieurs liens physiques "pointant" vers lui? (Les liens physiques ne "pointent" pas réellement, ce sont le même fichier, juste à un autre endroit dans la hiérarchie FS.) Une fois que execve () exécute avec succès un nouveau binaire, toutes les informations sur ses arguments sont perdues.

zvrba
la source
1
"Une fois qu'execve () exécute avec succès un nouveau binaire, toutes les informations sur ses arguments sont perdues." En fait, les arguments argp et envp ne sont pas perdus, ils sont passés en tant que argv [] et l'environnement et, dans certains UN * X, l'argument chemin ou quelque chose construit à partir de celui-ci est soit passé avec argp et envp (OS X / iOS, Solaris) ou mis à disposition via l'un des mécanismes répertoriés dans la réponse de mark4o. Mais, oui, cela vous donne juste l'un des liens durs s'il y en a plus d'un.
1

Vous pouvez utiliser argv [0] et analyser la variable d'environnement PATH. Regardez: Un échantillon d'un programme qui peut se trouver

facture
la source
7
Ce n'est pas vraiment fiable (bien que cela fonctionnera généralement avec des programmes lancés par les shells habituels), car execvet les parents prennent le chemin de l'exécutable séparément deargv
dmckee --- ex-moderator chaton
9
C'est une réponse incorrecte. Il peut vous dire où vous pouvez trouver un programme avec le même nom. Mais cela ne vous dit rien sur l'emplacement de l'exécutable en cours d'exécution.
Larry Gritz
0

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'ID de processus. Ps est également un utilitaire POSIX, il devrait donc être portable

donc 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-head - n'affiche pas de ligne d'en-tête, juste la sortie.

Le programme AC peut l'exécuter via popen.

MichaelMoser
la source
Il donne une chaîne de lancement complète avec des paramètres.
ETech
--no-head is non-portable
Good Person
1
ne fonctionne pas si le premier argument de execv n'est pas un chemin absolu.
hroptatyr
-4

Si vous utilisez C, vous pouvez utiliser la fonction getwd:

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

Cela vous imprimera sur la sortie standard, le répertoire courant de l'exécutable.

Lucas Ruelle
la source
3
au moins sous Windows, le répertoire de travail actuel n'a pas de relation particulière avec l'exécutable en cours d'exécution. Par exemple, CreateProcess peut lancer un .exe et définir son répertoire de travail de manière totalement indépendante.
Spike0xff
La situation est la même sur tous les autres OS: le répertoire courant est parfois le même que le répertoire exécutable par hasard, mais peut être complètement différent.
Lassi
-10

Le chemin de la valeur absolue d'un programme se trouve dans le PWD de l'envp de votre fonction principale, il y a aussi une fonction en C appelée getenv, donc il y a ça.

dacres
la source