Localizando o caminho do executável atual sem / proc / self / exe


190

Parece-me que o Linux é mais fácil com / proc / self / exe. Mas eu gostaria de saber se existe uma maneira conveniente de encontrar o diretório do aplicativo atual em C / C ++ com interfaces de plataforma cruzada. Eu já vi alguns projetos mexendo com argv [0], mas não parece totalmente confiável.

Se você já teve que suportar, digamos, o Mac OS X, que não possui / proc /, o que você teria feito? Use #ifdefs para isolar o código específico da plataforma (NSBundle, por exemplo)? Ou tente deduzir o caminho do executável de argv [0], $ PATH e outros enfeites, arriscando encontrar erros em casos extremos?



Eu pesquisei: pegue o meu ps -o comm. O que me trouxe aqui é: "/proc/pid/path/a.out"
bacia

A resposta do IMHO prideout merece estar no topo, porque aborda corretamente o requisito de "interfaces de plataforma cruzada" e é muito fácil de integrar.
Stéphane Gourichon 14/09/18

Respostas:


348

Algumas interfaces específicas do sistema operacional:

O método portátil (mas menos confiável) é usar argv[0]. Embora possa ser definido como qualquer coisa pelo programa de chamada, por convenção, é definido como um nome de caminho do executável ou um nome que foi encontrado usando $PATH.

Alguns shells, incluindo bash e ksh, definem a variável de ambiente " _" para o caminho completo do executável antes de ser executado. Nesse caso, você pode usar getenv("_")para obtê-lo. No entanto, isso não é confiável, porque nem todos os shells fazem isso, e pode ser definido como qualquer coisa ou ser deixado de um processo pai que não foi alterado antes da execução do programa.


3
E também observe que _NSGetExecutablePath () não segue links simbólicos.
Naruse

1
NetBSD: readlink / proc / curproc / exe Libélula BSD: readlink / proc / curproc / arquivo
naruse

6
Solaris: char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));; isso é diferente de getexecname()- o que equivale a pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.

4
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)" Esse não é o caminho do executável, é o nome do caminho do diretório por usuário em que os dados devem ser armazenados.

2
OpenBSD é o único onde ainda não pode, em 2017. Você tem que usar o PATH e argv [0] forma
Lothar

27

O uso de /proc/self/exenão é portátil e não é confiável. No meu sistema Ubuntu 12.04, você deve ser root para ler / seguir o link simbólico. Isso fará com que o exemplo do Boost e provavelmente as whereami()soluções postadas falhem.

Esta postagem é muito longa, mas discute os problemas reais e apresenta o código que realmente funciona junto com a validação em um conjunto de testes.

A melhor maneira de encontrar seu programa é refazer as mesmas etapas que o sistema usa. Isso é feito usando argv[0]resolvido em relação à raiz do sistema de arquivos, pwd, ambiente do caminho e considerando links simbólicos e canonização do nome do caminho. Isso é da memória, mas eu fiz isso no passado com sucesso e testei em uma variedade de situações diferentes. Não é garantido que funcione, mas se não houver, provavelmente você terá problemas muito maiores e é mais confiável do que qualquer outro método discutido. Existem situações em um sistema compatível com Unix em que o manuseio adequado deargv[0]não o levará ao seu programa, mas você estará executando em um ambiente com falha comprovada. Também é bastante portátil para todos os sistemas derivados do Unix desde 1970 e até para alguns sistemas não derivados do Unix, pois basicamente depende da funcionalidade padrão libc () e da linha de comando padrão. Deve funcionar no Linux (todas as versões), 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. E com uma pequena modificação, provavelmente VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2, etc. Se um programa foi iniciado diretamente a partir de um ambiente de GUI, ele deveria ter definido argv[0]um caminho absoluto.

Entenda que quase todo shell em qualquer sistema operacional compatível com Unix que já foi lançado basicamente encontra programas da mesma maneira e configura o ambiente operacional quase da mesma maneira (com alguns extras opcionais). Espera-se que qualquer outro programa que inicie um programa crie o mesmo ambiente (argv, strings de ambiente, etc.) para esse programa como se tivesse sido executado a partir de um shell, com alguns extras opcionais. Um programa ou usuário pode configurar um ambiente que se desvia desta convenção para outros programas subordinados que ele lança, mas, se o fizer, isso é um bug e o programa não tem expectativa razoável de que o programa subordinado ou seus subordinados funcionem corretamente.

Os possíveis valores de argv[0]incluem:

  • /path/to/executable - caminho absoluto
  • ../bin/executable - relativo a pwd
  • bin/executable - relativo a pwd
  • ./foo - relativo a pwd
  • executable - basename, encontre no caminho
  • bin//executable - relativo a pwd, não canônico
  • src/../bin/executable - relativo a pwd, não canônico, retorno
  • bin/./echoargc - relativo a pwd, não canônico

Valores que você não deve ver:

  • ~/bin/executable - reescrito antes que seu programa seja executado.
  • ~user/bin/executable - reescrito antes da execução do programa
  • alias - reescrito antes da execução do programa
  • $shellvariable - reescrito antes da execução do programa
  • *foo* - curinga, reescrito antes da execução do programa, não é muito útil
  • ?foo? - curinga, reescrito antes da execução do programa, não é muito útil

Além disso, eles podem conter nomes de caminhos não canônicos e várias camadas de links simbólicos. Em alguns casos, pode haver vários links físicos para o mesmo programa. Por exemplo, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, etc. pode ser hard links para /bin/busybox.

Para se encontrar, siga as etapas abaixo:

  • Salve pwd, PATH e argv [0] na entrada do seu programa (ou na inicialização da sua biblioteca), pois eles podem mudar mais tarde.

  • Opcional: particularmente para sistemas não Unix, separe, mas não descarte a parte do prefixo host / usuário / unidade do nome do caminho, se presente; a parte que muitas vezes precede dois pontos ou segue um "//" inicial.

  • Se argv[0]for um caminho absoluto, use-o como ponto de partida. Um caminho absoluto provavelmente começa com "/", mas em alguns sistemas não-Unix, ele pode começar com "\" ou uma letra de unidade ou prefixo de nome seguido por dois pontos.

  • Senão, se argv[0]for um caminho relativo (contém "/" ou "\", mas não começa com ele, como "../../bin/foo", em seguida, combine pwd + "/" + argv [0] (use diretório de trabalho atual a partir do momento em que o programa foi iniciado, não atual).

  • Caso contrário, se argv [0] for um nome básico simples (sem barras), combine-o com cada entrada na variável de ambiente PATH e tente-as e use a primeira que for bem-sucedida.

  • Opcional: Else experimentar o muito específico da plataforma /proc/self/exe, /proc/curproc/file(BSD), e (char *)getauxval(AT_EXECFN), e dlgetname(...)se estiver presente. Você pode até tentar esses argv[0]métodos antes , se eles estiverem disponíveis e você não encontrar problemas de permissão. No evento pouco provável (quando você considera todas as versões de todos os sistemas) em que elas estão presentes e não falham, elas podem ter mais autoridade.

  • Opcional: verifique o nome do caminho transmitido usando um parâmetro da linha de comandos.

  • Opcional: verifique se há um nome de caminho no ambiente transmitido explicitamente pelo script do wrapper, se houver.

  • Opcional: Como último recurso, tente a variável de ambiente "_". Pode apontar para um programa completamente diferente, como o shell do usuário.

  • Resolver links simbólicos, pode haver várias camadas. Existe a possibilidade de loops infinitos, mas se eles existirem, seu programa provavelmente não será chamado.

  • Canonize o nome do arquivo resolvendo substrings como "/foo/../bar/" para "/ bar /". Observe que isso pode alterar potencialmente o significado se você cruzar um ponto de montagem de rede, portanto, a canonização nem sempre é uma coisa boa. Em um servidor de rede, ".." no link simbólico pode ser usado para percorrer um caminho para outro arquivo no contexto do servidor em vez de no cliente. Nesse caso, você provavelmente deseja o contexto do cliente para que a canonização esteja correta. Também converta padrões como "/./" para "/" e "//" para "/". No shell, readlink --canonicalizeresolverá vários links simbólicos e canonizará o nome. O Chase pode ser semelhante, mas não está instalado. realpath()ou canonicalize_file_name(), se houver, pode ajudar.

Se realpath()não existir no momento da compilação, você pode emprestar uma cópia de uma distribuição de biblioteca licenciada permissivamente e compilá-la em si mesmo, em vez de reinventar a roda. Corrija o potencial estouro de buffer (passe no tamanho do buffer de saída, pense em strncpy () vs strcpy ()) se você estiver usando um buffer menor que PATH_MAX. Pode ser mais fácil usar uma cópia privada renomeada em vez de testar se ela existe. Cópia de licença permissiva do android / darwin / bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

Esteja ciente de que várias tentativas podem ser bem-sucedidas ou parcialmente bem-sucedidas e nem todas apontam para o mesmo executável; portanto, considere verificar seu executável; no entanto, você pode não ter permissão de leitura - se não conseguir lê-la, não a trate como uma falha. Ou verifique algo próximo ao seu executável, como o diretório "../lib/" que você está tentando encontrar. Você pode ter várias versões, versões compiladas e compiladas localmente, versões local e de rede e versões portáteis local e USB, etc. e há uma pequena possibilidade de obter dois resultados incompatíveis de diferentes métodos de localização. E "_" pode simplesmente apontar para o programa errado.

Um programa usando execvepode deliberadamente argv[0]ser incompatível com o caminho real usado para carregar o programa e danificar PATH, "_", pwd etc. etc., embora geralmente não haja muitas razões para fazê-lo; mas isso pode ter implicações de segurança se você tiver um código vulnerável que ignore o fato de que seu ambiente de execução pode ser alterado de várias maneiras, incluindo, sem limitação, a este (chroot, sistema de arquivos de fusíveis, links físicos, etc.) É possível para comandos do shell definir PATH, mas falham ao exportá-lo.

Você não precisa necessariamente codificar para sistemas não-Unix, mas seria uma boa idéia conhecer algumas das peculiaridades para poder escrever o código de forma que não seja tão difícil para alguém portar mais tarde . Esteja ciente de que alguns sistemas (DEC VMS, DOS, URLs etc.) podem ter nomes de unidades ou outros prefixos que terminam com dois pontos, como "C: \", "sys $ drive: [foo] bar" e "file : /// foo / bar / baz ". Os sistemas DEC VMS antigos usam "[" e "]" para incluir a parte do diretório do caminho, embora isso possa ter sido alterado se o seu programa for compilado em um ambiente POSIX. Alguns sistemas, como o VMS, podem ter uma versão do arquivo (separada por ponto e vírgula no final). Alguns sistemas usam duas barras consecutivas como em "// drive / caminho / para / arquivo" ou "usuário @ host: / caminho / para / arquivo" (comando scp) ou "arquivo: (delimitado por espaços) e "PATH" delimitado por dois pontos, mas seu programa deve receber PATH para que você não precise se preocupar com o caminho. O DOS e alguns outros sistemas podem ter caminhos relativos que começam com um prefixo de unidade. C: foo.exe refere-se a foo.exe no diretório atual na unidade C, portanto, você precisa procurar o diretório atual em C: e usá-lo para pwd. (delimitado por espaços) e "PATH" delimitado por dois pontos, mas seu programa deve receber PATH para que você não precise se preocupar com o caminho. O DOS e alguns outros sistemas podem ter caminhos relativos que começam com um prefixo de unidade. C: foo.exe refere-se a foo.exe no diretório atual da unidade C, portanto, você precisa procurar o diretório atual em C: e usá-lo para pwd.

Um exemplo de links simbólicos e wrappers no meu 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

Observe que a conta do usuário postou um link acima em um programa na HP que lida com os três casos básicos de argv[0]. Precisa de algumas mudanças, no entanto:

  • Será necessário reescrever tudo strcat()e strcpy()usar strncat()e strncpy(). Embora as variáveis ​​sejam declaradas com o comprimento PATHMAX, um valor de entrada do comprimento PATHMAX-1 mais o comprimento das seqüências de caracteres concatenadas é> PATHMAX e um valor de entrada do comprimento PATHMAX não será terminado.
  • Ele precisa ser reescrito como uma função de biblioteca, e não apenas para imprimir resultados.
    • Falha ao canonizar nomes (use o código de caminho real ao qual vinculei acima)
    • Falha ao resolver links simbólicos (use o código do caminho real)

Portanto, se você combinar o código HP e o código do caminho real e corrigir ambos para resistir a estouros de buffer, deverá ter algo que possa interpretar adequadamente argv[0].

A seguir, ilustramos os valores reais de argv[0]várias maneiras de chamar o mesmo programa no Ubuntu 12.04. E sim, o programa foi acidentalmente chamado de echoargc em vez de echoargv. Isso foi feito usando um script para cópia limpa, mas fazê-lo manualmente no shell obtém os mesmos resultados (exceto que os aliases não funcionam no script, a menos que você os habilite explicitamente).

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

Esses exemplos ilustram que as técnicas descritas neste post devem funcionar em uma ampla variedade de circunstâncias e por que algumas das etapas são necessárias.

EDIT: Agora, o programa que imprime argv [0] foi atualizado para realmente se encontrar.

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

E aqui está o resultado que demonstra que, em todos os testes anteriores, ele realmente se encontrou.

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"

Os dois lançamentos da GUI descritos acima também encontram corretamente o programa.

Existe uma armadilha em potencial. A access()função descarta as permissões se o programa estiver configurado antes do teste. Se houver uma situação em que o programa possa ser encontrado como um usuário elevado, mas não como um usuário comum, poderá haver uma situação em que esses testes falharão, embora seja improvável que o programa possa realmente ser executado nessas circunstâncias. Pode-se usar euidaccess (). É possível, no entanto, encontrar um programa inacessível mais cedo do que o usuário real.


1
Você coloca muito esforço nisso - bem feito. Infelizmente, nem strncpy()nem (especialmente) strncat()são usados ​​com segurança no código. strncpy()não garante rescisão nula; se a sequência de origem for maior que o espaço de destino, a sequência não será nula terminada. strncat()é muito difícil de usar; strncat(target, source, sizeof(target))está errado (mesmo que targetseja uma sequência vazia) se sourcefor maior que o destino. O comprimento é o número de caracteres que podem ser anexados com segurança ao destino, excluindo o nulo à direita, assim sizeof(target)-1como o máximo.
Jonathan Leffler

4
O código strncpy está correto, diferente do método que você sugere que eu deveria usar. Eu sugiro que você leia o código com mais cuidado. Ele não transborda buffers nem os deixa não terminados. Cada uso de strncpy () / stncat () é limitado pela cópia sizeof (buffer), que é válida, e o último caractere do buffer é preenchido com zero substituindo o último caractere do buffer. strncat (), no entanto, usa o parâmetro size incorretamente como uma contagem e pode estourar devido ao fato de que antecede os ataques de estouro de buffer.
Whitis

"sudo apt-get install libbsd0 libbsd-dev", então s / strncat / strlcat /
whitis 27/03

1
Não use PATH_MAX. Isso parou de funcionar há 30 anos, sempre use malloc.
Lothar

Além disso, se você usar uma chamada init. Resolva totalmente o caminho para o exe no init e não apenas uma parte e faça-o posteriormente mais tarde. Nenhuma avaliação lenta aqui é possível se você usar o caminho real no resolvedor. Juntamente com os outros erros, simplesmente o pior código que eu já vi no stackoverflow em uma resposta longa.
Lothar

13

Confira a biblioteca whereami de Gregory Pakosz (que possui apenas um único arquivo C); Ele permite que você obtenha o caminho completo para o executável atual em uma variedade de plataformas. Atualmente, está disponível como um repositório no github aqui .


8

Uma alternativa no Linux para usar /proc/self/exeou argv[0]está usando as informações transmitidas pelo intérprete ELF, disponibilizadas pela glibc como tal:

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

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

Observe que getauxvalé uma extensão glibc e, para ser robusto, você deve verificar se não retorna NULL(indicando que o interpretador ELF não forneceu o AT_EXECFNparâmetro), mas não acho que isso seja realmente um problema no Linux.


Eu gosto disso, pois é simples e o glibc está incluído no Gtk + de qualquer maneira (o que eu estou usando).
Colin Keenan

4

Se você já teve que suportar, digamos, o Mac OS X, que não possui / proc /, o que você teria feito? Use #ifdefs para isolar o código específico da plataforma (NSBundle, por exemplo)?

Sim, isolar o código específico da plataforma #ifdefsé a maneira convencional de fazer isso.

Outra abordagem seria ter um #ifdefcabeçalho sem limpeza que contenha declarações de função e colocar as implementações em arquivos de origem específicos da plataforma. Por exemplo, confira como a biblioteca Poco C ++ faz algo semelhante para a classe Environment .


4

Tornar esse trabalho confiável entre plataformas requer o uso de instruções #ifdef.

O código abaixo localiza o caminho do executável no Windows, Linux, MacOS, Solaris ou FreeBSD (embora o FreeBSD não tenha sido testado). Ele usa o boost > = 1.55.0 para simplificar o código, mas é fácil de remover, se você quiser. Basta usar define como _MSC_VER e __linux conforme o SO e o compilador exigem.

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

A versão acima retorna caminhos completos, incluindo o nome do executável. Se você deseja o caminho sem o nome do executável, #include boost/filesystem.hpp>altere a instrução de retorno para:

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

@ Frank, não sei por que você diz isso. Funciona para mim. Eu vi outra resposta afirmando que você precisa de root para acessar / proc / self / exe, mas não achei isso em nenhum sistema Linux que eu tentei (CentOS ou Mint).
jtbr

2

Dependendo da versão do QNX Neutrino , existem diferentes maneiras de encontrar o caminho completo e o nome do arquivo executável usado para iniciar o processo em execução. Denoto o identificador do processo como <PID>. Tente o seguinte:

  1. Se o arquivo /proc/self/exefileexistir, seu conteúdo será a informação solicitada.
  2. Se o arquivo /proc/<PID>/exefileexistir, seu conteúdo será a informação solicitada.
  3. Se o arquivo /proc/self/asexistir, então:
    1. open() o arquivo.
    2. Aloque um buffer de, pelo menos sizeof(procfs_debuginfo) + _POSIX_PATH_MAX,.
    3. Dê esse buffer como entrada para devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Lance o buffer para a procfs_debuginfo*.
    5. As informações solicitadas estão no pathcampo da procfs_debuginfoestrutura. Aviso : Por algum motivo, às vezes, o QNX omite a primeira barra /do caminho do arquivo. Anexe isso /quando necessário.
    6. Limpe (feche o arquivo, libere o buffer etc.).
  4. Tente o procedimento 3.com o arquivo /proc/<PID>/as.
  5. Tente dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)onde dlinfoestá uma Dl_infoestrutura que dli_fnamepode conter as informações solicitadas.

Eu espero que isso ajude.


1

AFAIK, de nenhuma maneira. E também há uma ambiguidade: o que você gostaria de obter como resposta se o mesmo executável tiver vários links físicos "apontando" para ele? (Na verdade, os links físicos não "apontam", eles são o mesmo arquivo, apenas em outro lugar na hierarquia do FS.) Quando execve () executa com êxito um novo binário, todas as informações sobre seus argumentos são perdidas.


1
"Uma vez que execve () executa com sucesso um novo binário, todas as informações sobre seus argumentos são perdidas." Na verdade, os argumentos argp e envp não são perdidos, são passados ​​como argv [] e pelo ambiente e, em alguns UN * Xes, o argumento do nome do caminho ou algo construído a partir dele é passado junto com argp e envp (OS X / iOS, Solaris) ou disponibilizados através de um dos mecanismos listados na resposta do mark4o. Mas sim, isso fornece apenas um dos links físicos, se houver mais de um.

1

Você pode usar argv [0] e analisar a variável de ambiente PATH. Veja: Uma amostra de um programa que pode se encontrar


7
Esta não é realmente confiável (embora ele geralmente trabalho com programas lançados pelas conchas habituais), porque execve parentes tomar o caminho para o executável separadamente deargv
dmckee --- ex-moderador gatinho

9
Esta é uma resposta incorreta. Pode indicar onde você pode encontrar um programa com o mesmo nome. Mas ele não diz nada sobre onde realmente mora o executável atualmente em execução.
Larry Gritz

0

Maneira mais portátil de obter o nome do caminho da imagem executável:

O ps pode fornecer o caminho do executável, desde que você tenha o ID do processo. Também ps é um utilitário POSIX, por isso deve ser portátil

portanto, se o ID do processo for 249297, esse comando fornecerá apenas o nome do caminho.

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

Explicação de argumentos

-p - seleciona o processo fornecido

-o comm - exibe o nome do comando (-o cmd seleciona toda a linha de comandos)

--no-header - não exibe uma linha de cabeçalho, apenas a saída.

O programa CA pode executar isso via popen.


Ele fornece uma sequência de inicialização completa com parâmetros.
ETech

--no-header não é portátil
Boa pessoa

1
não funciona se o primeiro argumento para execv não for um caminho absoluto.
Hroptatyr

-4

Se você usa C, pode usar a função getwd:

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

Isso imprimirá na saída padrão, o diretório atual do executável.


3
pelo menos no Windows, o diretório de trabalho atual não possui nenhuma relação específica com o executável em execução. Por exemplo, CreateProcess pode iniciar um .exe e definir seu diretório de trabalho de forma completamente independente.
precisa saber é o seguinte

A situação é a mesma em todos os outros sistemas operacionais: o diretório atual às vezes é o mesmo que o diretório executável por acaso, mas pode ser completamente diferente.
Lassi

-10

O caminho do valor absoluto de um programa está no PWD do envp de sua função principal, também existe uma função em C chamada getenv, então existe isso.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.