Encontrar la ruta del ejecutable actual sin / proc / self / exe

190

Me parece que Linux lo tiene fácil con / proc / self / exe. Pero me gustaría saber si hay una manera conveniente de encontrar el directorio de la aplicación actual en C / C ++ con interfaces multiplataforma. He visto algunos proyectos jugando con argv [0], pero no parece del todo confiable.

Si alguna vez tuvieras que soportar, por ejemplo, Mac OS X, que no tiene / proc /, ¿qué hubieras hecho? ¿Usa #ifdefs para aislar el código específico de la plataforma (NSBundle, por ejemplo)? ¿O intente deducir la ruta del ejecutable de argv [0], $ PATH y otras cosas, arriesgándose a encontrar errores en casos extremos?

Uros Dimitrijevic
fuente
Duplicado: stackoverflow.com/questions/933850/…
Greg Hewgill
Busqué en Google: consigue mi ps -o comm. Lo que me trajo aquí es: "/proc/pid/path/a.out"
cuenca
La respuesta de IMHO prideout merece estar en la cima, porque aborda correctamente el requisito de "interfaces multiplataforma" y es muy fácil de integrar.
Stéphane Gourichon

Respuestas:

348

Algunas interfaces específicas del sistema operativo:

El método portátil (pero menos confiable) es usar argv[0]. Si bien el programa que realiza la llamada puede establecerlo en cualquier cosa, por convención se configura como un nombre de ruta del ejecutable o un nombre que se encontró utilizando$PATH .

Algunos shells, incluidos bash y ksh, establecen la variable de entorno " _" en la ruta completa del ejecutable antes de que se ejecute. En ese caso puedes usarlo getenv("_")para conseguirlo. Sin embargo, esto no es confiable porque no todos los shells hacen esto, y podría establecerse en cualquier cosa o dejarse de un proceso principal que no lo cambió antes de ejecutar su programa.

mark4o
fuente
3
Y también tenga en cuenta que _NSGetExecutablePath () no sigue enlaces simbólicos.
Naruse
1
NetBSD: readlink / proc / curproc / exe DragonFly BSD: readlink / proc / curproc / file
naruse
66
Solaris: char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));; eso es diferente de getexecname()- lo que hace el equivalente de pargs -x <PID> | grep AT_SUN_EXECNAME...
Frankh.
44
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)" Esa no es la ruta del ejecutable, es el nombre de la ruta del directorio por usuario donde se deben almacenar los datos.
2
OpenBSD es el único en el que aún no puedes hacerlo en 2017. Tienes que usar el camino y la forma argv [0]
Lothar
27

El uso de /proc/self/exeno 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 --canonicalizeresolverá 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 execvepuede 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.

whitis
fuente
1
Pusiste mucho esfuerzo en eso, bien hecho. Desafortunadamente, ni strncpy()ni (especialmente) strncat()se usa de manera segura en el código. strncpy()no garantiza la terminación nula; Si la cadena de origen es más larga que el espacio de destino, la cadena no tiene terminación nula. strncat()es muy dificil de usar; strncat(target, source, sizeof(target))está mal (incluso si targetes una cadena vacía para empezar) si sourcees más larga que el objetivo. La longitud es el número de caracteres que se pueden agregar de forma segura al objetivo, excluyendo el nulo final, por lo que sizeof(target)-1es el máximo.
Jonathan Leffler
44
El código strncpy es correcto, a diferencia del método que implica que debería usar. Le sugiero que lea el código con más cuidado. No desborda los búferes ni los deja sin terminar. Cada uso de strncpy () / stncat () está limitado al copiar sizeof (buffer), que es válido, y luego el último carácter del buffer se llena con un cero sobrescribiendo el último carácter del buffer. strncat (), sin embargo, usa el parámetro de tamaño incorrectamente como un recuento y puede desbordarse debido al hecho de que es anterior a los ataques de desbordamiento del búfer.
whitis
"sudo apt-get install libbsd0 libbsd-dev", luego s / strncat / strlcat /
whitis
1
No use PATH_MAX. Esto dejó de funcionar hace 30 años, siempre usa malloc.
Lothar
También si usa una llamada de inicio. Resuelva completamente la ruta al exe en init y no solo una parte y luego hágalo más tarde en la llamada. No es posible una evaluación diferida aquí si usa realpath en el resolutor Junto con los otros errores simplemente el peor código que he visto en stackoverflow en una respuesta larga.
Lothar
13

Echa un vistazo a la biblioteca whereami de Gregory Pakosz (que tiene un solo archivo C); le permite obtener la ruta completa al ejecutable actual en una variedad de plataformas. Actualmente, está disponible como un repositorio en github aquí .

orgullo
fuente
8

Una alternativa en Linux para usar cualquiera /proc/self/exeo argv[0]es usar la información pasada por el intérprete ELF, que glibc pone a disposición como tal:

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

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

Tenga en cuenta que getauxvales una extensión glibc, y para ser robusto, debe verificar que no regrese NULL(lo que indica que el intérprete ELF no ha proporcionado el AT_EXECFNparámetro), pero no creo que esto sea realmente un problema en Linux.

Dolda2000
fuente
Me gusta esto ya que es simple y glibc se incluye con Gtk + de todos modos (que estoy usando).
Colin Keenan
4

Si alguna vez tuvieras que soportar, por ejemplo, Mac OS X, que no tiene / proc /, ¿qué hubieras hecho? ¿Usa #ifdefs para aislar el código específico de la plataforma (NSBundle, por ejemplo)?

Sí, aislar el código específico de la plataforma #ifdefses la forma convencional de hacerlo.

Otro enfoque sería tener un #ifdefencabezado sin limpieza que contenga declaraciones de funciones y coloque las implementaciones en archivos fuente específicos de la plataforma. Por ejemplo, vea cómo la biblioteca Poco C ++ hace algo similar para su clase de entorno .

Apilado
fuente
4

Hacer que esto funcione de manera confiable en todas las plataformas requiere el uso de declaraciones #ifdef.

El siguiente código encuentra la ruta del ejecutable en Windows, Linux, MacOS, Solaris o FreeBSD (aunque FreeBSD no ha sido probado). Utiliza boost > = 1.55.0 para simplificar el código, pero es lo suficientemente fácil de eliminar si lo desea. Simplemente use definiciones como _MSC_VER y __linux según lo requiera el sistema operativo y el compilador.

#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 versión anterior devuelve rutas completas, incluido el nombre del ejecutable. Si, en cambio, desea la ruta sin el nombre del ejecutable #include boost/filesystem.hpp>y cambia la declaración de retorno a:

return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();
jtbr
fuente
@ Frank, no estoy seguro de por qué dices eso. Funciona para mi. Vi otra respuesta que dice que necesita root para acceder / proc / self / exe, pero no lo he encontrado en ningún sistema Linux que haya probado (CentOS o Mint).
jtbr
2

Dependiendo de la versión de QNX Neutrino , hay diferentes formas de encontrar la ruta completa y el nombre del archivo ejecutable que se utilizó para iniciar el proceso de ejecución. Denote el identificador de proceso como <PID>. Intenta lo siguiente:

  1. Si el archivo /proc/self/exefileexiste, su contenido es la información solicitada.
  2. Si el archivo /proc/<PID>/exefileexiste, su contenido es la información solicitada.
  3. Si el archivo /proc/self/asexiste, entonces:
    1. open() el archivo.
    2. Asigne un búfer de, al menos sizeof(procfs_debuginfo) + _POSIX_PATH_MAX,.
    3. Dale a ese búfer como entrada para devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Echa el búfer a a procfs_debuginfo*.
    5. La información solicitada está en el pathcampo de la procfs_debuginfoestructura. Advertencia : por alguna razón, a veces, QNX omite la primera barra diagonal /de la ruta del archivo. Anteponga eso /cuando sea necesario.
    6. Limpie (cierre el archivo, libere el búfer, etc.).
  4. Pruebe el procedimiento 3.con el archivo /proc/<PID>/as.
  5. Pruebe dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)dónde dlinfoestá una Dl_infoestructura cuyo contenido dli_fnamepodría contener la información solicitada.

Espero que esto ayude.

Dr. Koutheir Attouchi
fuente
1

AFAIK, no es así. Y también hay una ambigüedad: ¿qué le gustaría obtener como respuesta si el mismo ejecutable tiene múltiples enlaces duros "apuntando"? (Los enlaces duros en realidad no "apuntan", son el mismo archivo, solo que en otro lugar de la jerarquía FS.) Una vez que execve () ejecuta con éxito un nuevo binario, se pierde toda la información sobre sus argumentos.

zvrba
fuente
1
"Una vez que execve () ejecuta con éxito un nuevo binario, se pierde toda la información sobre sus argumentos". En realidad, los argumentos argp y envp no se pierden, se pasan como argv [] y el entorno y, en algunos UN * Xes, el argumento de nombre de ruta o algo construido a partir de él se pasa junto con argp y envp (OS X / iOS, Solaris) o disponible a través de uno de los mecanismos enumerados en la respuesta de mark4o. Pero sí, eso solo te da uno de los enlaces duros si hay más de uno.
1

Puede usar argv [0] y analizar la variable de entorno PATH. Mire: una muestra de un programa que puede encontrarse

cuenta
fuente
77
Esto no es realmente confiable (aunque generalmente funcionará con programas lanzados por los shells habituales), ya que los execvparientes toman el camino hacia el ejecutable por separado deargv
dmckee --- ex-moderator kitten el
9
Esta es una respuesta incorrecta. Puede decirle dónde puede encontrar un programa con el mismo nombre. Pero no le dice nada sobre dónde vive realmente el ejecutable actualmente en ejecución.
Larry Gritz
0

Forma más portátil de obtener el nombre de ruta de la imagen ejecutable:

ps puede darle la ruta del ejecutable, dado que tiene la identificación del proceso. Ps también es una utilidad POSIX, por lo que debería ser portátil

entonces, si la identificación del proceso es 249297, entonces este comando solo le da el nombre de la ruta.

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

Explicación de argumentos

-p: selecciona el proceso dado

-o comm: muestra el nombre del comando (-o cmd selecciona la línea de comando completa)

--no-header - no muestra una línea de encabezado, solo la salida.

El programa de CA puede ejecutar esto a través de popen.

MichaelMoser
fuente
Da una cadena de lanzamiento completa con params.
ETech
--no-header no es portátil
Good Person
1
no funciona si el primer argumento para execv no es una ruta absoluta.
hroptatyr
-4

Si usa C, puede usar la función getwd:

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

Esto imprimirá en la salida estándar, el directorio actual del ejecutable.

Lucas Ruelle
fuente
3
al menos en Windows, el directorio de trabajo actual no tiene una relación particular con el ejecutable en ejecución. Por ejemplo, CreateProcess puede iniciar un archivo .exe y establecer su directorio de trabajo de manera completamente independiente.
Spike0xff
La situación es la misma en todos los demás sistemas operativos: el directorio actual a veces es el mismo que el directorio ejecutable por casualidad, pero puede ser completamente diferente.
Lassi
-10

La ruta de valor absoluto de un programa está en el PWD del entorno de su función principal, también hay una función en C llamada getenv, así que ahí está.

dacres
fuente