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?
ps -o comm
. Ce qui m'a amené ici est: "/proc/pid/path/a.out"Réponses:
Certaines interfaces spécifiques au système d'exploitation:
_NSGetExecutablePath()
( man 3 dyld )readlink /proc/self/exe
getexecname()
sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1
readlink /proc/curproc/file
(FreeBSD n'a pas procfs par défaut)readlink /proc/curproc/exe
readlink /proc/curproc/file
GetModuleFileName()
avechModule
=NULL
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 utilisergetenv("_")
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.la source
char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));
; c'est différent degetexecname()
- ce qui équivaut àpargs -x <PID> | grep AT_SUN_EXECNAME
...L'utilisation de
/proc/self/exe
n'est pas portable et peu fiable. Sur mon système Ubuntu 12.04, vous devez être root pour lire / suivre le lien symbolique. Cela ferawhereami()
é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éfiniargv[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 à pwdbin/executable
- par rapport à pwd./foo
- par rapport à pwdexecutable
- nom de base, trouver dans le cheminbin//executable
- par rapport à pwd, non canoniquesrc/../bin/executable
- par rapport à pwd, non canonique, retour arrièrebin/./echoargc
- par rapport à pwd, non canoniqueValeurs 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 programmealias
- 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 utileEn 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 ledlgetname(...)
cas échéant. Vous pouvez même essayer cesargv[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 --canonicalize
résoudra plusieurs liens symboliques et canonisera le nom. Chase peut faire la même chose mais n'est pas installé.realpath()
oucanonicalize_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.cSachez 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
execve
peut délibérément être définiargv[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:
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:strcat()
etstrcpy()
utiliserstrncat()
etstrncpy()
. 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.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).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.
Et voici le résultat qui démontre que dans chacun des tests précédents, il s'est réellement retrouvé.
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.la source
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 sitarget
c'est une chaîne vide pour commencer) sisource
est 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 ensizeof(target)-1
est de même pour le maximum.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 .
la source
Une alternative sous Linux à l'utilisation de l'un
/proc/self/exe
ou l' autre ouargv[0]
utilise les informations transmises par l'interpréteur ELF, mises à disposition par la glibc en tant que telles:Notez que
getauxval
s'agit d'une extension glibc, et pour être robuste, vous devez vérifier qu'elle ne retourne pasNULL
(indiquant que l'interpréteur ELF n'a pas fourni leAT_EXECFN
paramètre), mais je ne pense pas que ce soit réellement un problème sous Linux.la source
Oui, isoler le code spécifique à la plate-forme
#ifdefs
est la manière conventionnelle de procéder.Une autre approche consisterait à avoir un en-
#ifdef
tê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 .la source
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.
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:la source
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:/proc/self/exefile
existe, son contenu correspond aux informations demandées./proc/<PID>/exefile
existe, son contenu correspond aux informations demandées./proc/self/as
existe, alors:open()
le fichier.sizeof(procfs_debuginfo) + _POSIX_PATH_MAX
.devctl(fd, DCMD_PROC_MAPDEBUG_BASE,...
.procfs_debuginfo*
.path
champ de laprocfs_debuginfo
structure. Avertissement : pour une raison quelconque, QNX omet parfois la première barre oblique/
du chemin du fichier. Préparez cela/
si nécessaire.3.
avec le fichier/proc/<PID>/as
.dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)
où sedlinfo
trouve uneDl_info
structure quidli_fname
pourrait contenir les informations demandées.J'espère que ça aide.
la source
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.
la source
Vous pouvez utiliser argv [0] et analyser la variable d'environnement PATH. Regardez: Un échantillon d'un programme qui peut se trouver
la source
execv
et les parents prennent le chemin de l'exécutable séparément deargv
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.
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.
la source
Si vous utilisez C, vous pouvez utiliser la fonction getwd:
Cela vous imprimera sur la sortie standard, le répertoire courant de l'exécutable.
la source
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.
la source