El uso de /proc/self/exe
no es portátil y no es confiable. En mi sistema Ubuntu 12.04, debe ser root para leer / seguir el enlace simbólico. Esto hará que el ejemplo de Boost y probablemente las whereami()
soluciones publicadas fallen.
Esta publicación es muy larga, pero analiza los problemas reales y presenta el código que realmente funciona junto con la validación contra un conjunto de pruebas.
La mejor manera de encontrar su programa es volver sobre los mismos pasos que utiliza el sistema. Esto se hace mediante el uso argv[0]
resuelto contra la raíz del sistema de archivos, pwd, entorno de ruta y considerando enlaces simbólicos y canonicalización de nombre de ruta. Esto es de memoria, pero lo he hecho en el pasado con éxito y lo probé en una variedad de situaciones diferentes. No se garantiza que funcione, pero si no es así, probablemente tenga problemas mucho mayores y, en general, es más confiable que cualquiera de los otros métodos discutidos. Hay situaciones en un sistema compatible con Unix en el que el manejo adecuado deargv[0]
no lo llevará a su programa, pero luego se está ejecutando en un entorno roto que puede certificarse. También es bastante portátil para todos los sistemas derivados de Unix desde alrededor de 1970 e incluso algunos sistemas no derivados de Unix, ya que básicamente se basa en la funcionalidad estándar libc () y la funcionalidad de línea de comandos estándar. Debería funcionar en Linux (todas las versiones), Android, Chrome OS, Minix, Bell Labs Unix original, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Nextstep, etc. Y con una pequeña modificación, probablemente VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2, etc. Si un programa se lanzó directamente desde un entorno GUI, debería haberse establecido argv[0]
en una ruta absoluta.
Comprenda que casi todos los shell de todos los sistemas operativos compatibles con Unix que se han lanzado básicamente encuentran los programas de la misma manera y configuran el entorno operativo de la misma manera (con algunos extras opcionales). Y se espera que cualquier otro programa que inicie un programa cree el mismo entorno (argv, cadenas de entorno, etc.) para ese programa como si se ejecutara desde un shell, con algunos extras opcionales. Un programa o usuario puede configurar un entorno que se desvía de esta convención para otros programas subordinados que inicia, pero si lo hace, es un error y el programa no tiene una expectativa razonable de que el programa subordinado o sus subordinados funcionarán correctamente.
Los posibles valores de argv[0]
incluyen:
/path/to/executable
- camino absoluto
../bin/executable
- relativo a pwd
bin/executable
- relativo a pwd
./foo
- relativo a pwd
executable
- nombre base, encontrar en el camino
bin//executable
- relativo a pwd, no canónico
src/../bin/executable
- relativo a pwd, no canónico, retroceso
bin/./echoargc
- relativo a pwd, no canónico
Valores que no deberías ver:
~/bin/executable
- reescrito antes de que se ejecute su programa.
~user/bin/executable
- reescrito antes de que se ejecute tu programa
alias
- reescrito antes de que se ejecute tu programa
$shellvariable
- reescrito antes de que se ejecute tu programa
*foo*
- comodín, reescrito antes de que se ejecute el programa, no muy útil
?foo?
- comodín, reescrito antes de que se ejecute el programa, no muy útil
Además, estos pueden contener nombres de ruta no canónicos y múltiples capas de enlaces simbólicos. En algunos casos, puede haber múltiples enlaces duros al mismo programa. Por ejemplo, /bin/ls
, /bin/ps
, /bin/chmod
, /bin/rm
, etc., pueden ser enlaces duros /bin/busybox
.
Para encontrarse, siga los pasos a continuación:
Guarde pwd, PATH y argv [0] en la entrada a su programa (o inicialización de su biblioteca) ya que pueden cambiar más adelante.
Opcional: particularmente para sistemas que no son Unix, separe pero no descarte la parte del prefijo host / usuario / unidad de nombre de ruta, si está presente; la parte que a menudo precede a dos puntos o sigue a un "//" inicial.
Si argv[0]
es una ruta absoluta, úsela como punto de partida. Una ruta absoluta probablemente comienza con "/" pero en algunos sistemas que no son Unix puede comenzar con "\" o una letra de unidad o prefijo de nombre seguido de dos puntos.
De lo contrario, si argv[0]
es una ruta relativa (contiene "/" o "\" pero no comienza con ella, como "../../bin/foo", luego combine pwd + "/" + argv [0] (use presente directorio de trabajo desde el inicio del programa, no actual).
De lo contrario, si argv [0] es un nombre base simple (sin barras), entonces combínelo con cada entrada en la variable de entorno PATH y pruebe esas y use la primera que tenga éxito.
Opcional: Else tratar la específica plataforma muy /proc/self/exe
, /proc/curproc/file
(BSD), y (char *)getauxval(AT_EXECFN)
, y dlgetname(...)
si está presente. Incluso puede probar estos argv[0]
métodos anteriores, si están disponibles y no encuentra problemas de permisos. En el caso poco probable (si considera todas las versiones de todos los sistemas) que están presentes y no fallan, podrían ser más autoritativos.
Opcional: verifique el nombre de la ruta que se pasa utilizando un parámetro de línea de comando.
Opcional: compruebe si hay un nombre de ruta en el entorno que su secuencia de comandos pasa explícitamente, si corresponde.
Opcional: como último recurso, pruebe la variable de entorno "_". Puede apuntar a un programa completamente diferente, como el shell de los usuarios.
Resolver enlaces simbólicos, puede haber varias capas. Existe la posibilidad de bucles infinitos, aunque si existen, su programa probablemente no será invocado.
Canonicalice el nombre de archivo resolviendo subcadenas como "/foo/../bar/" a "/ bar /". Tenga en cuenta que esto puede cambiar el significado si cruza un punto de montaje de red, por lo que la canonización no siempre es algo bueno. En un servidor de red, ".." en el enlace simbólico se puede usar para recorrer una ruta a otro archivo en el contexto del servidor en lugar de hacerlo en el cliente. En este caso, probablemente desee el contexto del cliente, por lo que la canonicalización está bien. También convierta patrones como "/./" a "/" y "//" a "/". En shell, readlink --canonicalize
resolverá múltiples enlaces simbólicos y canonizará el nombre. Chase puede hacer algo similar pero no está instalado. realpath()
o canonicalize_file_name()
, si está presente, puede ayudar.
Si realpath()
no existe en el momento de la compilación, puede pedir prestada una copia de una distribución de biblioteca autorizada y autorizada, y compilarla usted mismo en lugar de reinventar la rueda. Arregle el desbordamiento potencial del búfer (pase el tamaño del búfer de salida, piense en strncpy () vs strcpy ()) si va a usar un búfer menor que PATH_MAX. Puede ser más fácil usar una copia privada renombrada en lugar de probar si existe. Copia de licencia permisiva de android / darwin / bsd:
https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
Tenga en cuenta que varios intentos pueden ser exitosos o parcialmente exitosos y es posible que no todos apunten al mismo ejecutable, así que considere verificar su ejecutable; sin embargo, es posible que no tenga permiso de lectura; si no puede leerlo, no lo trate como un error. O verifique algo cerca de su ejecutable, como el directorio "../lib/" que está tratando de encontrar. Es posible que tenga varias versiones, versiones compiladas y compiladas localmente, versiones locales y de red, y versiones portátiles de unidades locales y USB, etc. y existe una pequeña posibilidad de que obtenga dos resultados incompatibles de diferentes métodos de localización. Y "_" puede simplemente señalar el programa incorrecto.
Un programa que usa execve
puede establecer deliberadamenteargv[0]
para que sea incompatible con la ruta real utilizada para cargar el programa y corromper PATH, "_", pwd, etc. aunque generalmente no hay muchas razones para hacerlo; pero esto podría tener implicaciones de seguridad si tiene un código vulnerable que ignora el hecho de que su entorno de ejecución se puede cambiar de varias maneras, incluyendo, entre otras, esta (chroot, sistema de archivos de fusibles, enlaces duros, etc.) Es posible para que los comandos de shell establezcan PATH pero no lo exporten.
No es necesario que codifique para sistemas que no son Unix, pero sería una buena idea conocer algunas de las peculiaridades para poder escribir el código de tal manera que no sea tan difícil que alguien lo transfiera más tarde. . Tenga en cuenta que algunos sistemas (DEC VMS, DOS, URL, etc.) pueden tener nombres de unidades u otros prefijos que terminan con dos puntos como "C: \", "sys $ drive: [foo] bar" y "file : /// foo / bar / baz ". Los sistemas DEC VMS antiguos usan "[" y "]" para encerrar la parte del directorio de la ruta, aunque esto puede haber cambiado si su programa se compila en un entorno POSIX. Algunos sistemas, como VMS, pueden tener una versión de archivo (separados por un punto y coma al final). Algunos sistemas utilizan dos barras diagonales consecutivas como en "// unidad / ruta / a / archivo" o "usuario @ host: / ruta / a / archivo" (comando scp) o "archivo: (delimitado con espacios) y "PATH" delimitado con dos puntos, pero su programa debe recibir PATH para que no tenga que preocuparse por la ruta. DOS y algunos otros sistemas pueden tener rutas relativas que comienzan con un prefijo de unidad. C: foo.exe se refiere a foo.exe en el directorio actual en la unidad C, por lo que debe buscar el directorio actual en C: y usarlo para pwd. (delimitado con espacios) y "PATH" delimitado con dos puntos, pero su programa debe recibir PATH para que no tenga que preocuparse por la ruta. DOS y algunos otros sistemas pueden tener rutas relativas que comienzan con un prefijo de unidad. C: foo.exe se refiere a foo.exe en el directorio actual en la unidad C, por lo que debe buscar el directorio actual en C: y usarlo para pwd.
Un ejemplo de enlaces simbólicos y envoltorios en mi sistema:
/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
Tenga en cuenta que la factura del usuario publicó un enlace arriba a un programa en HP que maneja los tres casos básicos de argv[0]
. Sin embargo, necesita algunos cambios:
- Será necesario reescribir todo
strcat()
y strcpy()
usar strncat()
y strncpy()
. Aunque las variables se declaran de longitud PATHMAX, un valor de entrada de longitud PATHMAX-1 más la longitud de las cadenas concatenadas es> PATHMAX y un valor de entrada de longitud PATHMAX no estaría terminado.
- Debe reescribirse como una función de biblioteca, en lugar de simplemente imprimir resultados.
- No puede canonizar los nombres (use el código de ruta real que he vinculado anteriormente)
- No resuelve los enlaces simbólicos (use el código realpath)
Entonces, si combina tanto el código de HP como el código de ruta real y arregla ambos para que sean resistentes a los desbordamientos del búfer, entonces debe tener algo que pueda interpretar correctamente argv[0]
.
A continuación se ilustran los valores reales de argv[0]
varias formas de invocar el mismo programa en Ubuntu 12.04. Y sí, el programa se llamó accidentalmente echoargc en lugar de echoargv. Esto se hizo usando un script para una copia limpia, pero hacerlo manualmente en el shell obtiene los mismos resultados (excepto que los alias no funcionan en el script a menos que los habilites explícitamente).
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
Estos ejemplos ilustran que las técnicas descritas en esta publicación deberían funcionar en una amplia gama de circunstancias y por qué algunos de los pasos son necesarios.
EDITAR: Ahora, el programa que imprime argv [0] se ha actualizado para encontrarse realmente.
// 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 */
}
Y aquí está el resultado que demuestra que en cada una de las pruebas anteriores se encontró realmente.
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"
Los dos lanzamientos de GUI descritos anteriormente también encuentran correctamente el programa.
Hay un peligro potencial. La access()
función elimina los permisos si el programa se establece antes de la prueba. Si hay una situación en la que el programa se puede encontrar como un usuario elevado pero no como un usuario normal, entonces puede haber una situación en la que estas pruebas fallarían, aunque es poco probable que el programa se ejecute en esas circunstancias. Uno podría usar euidaccess () en su lugar. Sin embargo, es posible que encuentre un programa inaccesible antes en la ruta que el usuario real.