O uso de /proc/self/exe
nã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 --canonicalize
resolverá 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 execve
pode 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.