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?
ps -o comm
. O que me trouxe aqui é: "/proc/pid/path/a.out"Respostas:
Algumas interfaces específicas do sistema operacional:
_NSGetExecutablePath()
( man 3 dyld )readlink /proc/self/exe
getexecname()
sysctl CTL_KERN KERN_PROC KERN_PROC_PATHNAME -1
readlink /proc/curproc/file
(FreeBSD não possui procfs por padrão)readlink /proc/curproc/exe
readlink /proc/curproc/file
GetModuleFileName()
comhModule
=NULL
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 usargetenv("_")
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.fonte
char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));
; isso é diferente degetexecname()
- o que equivale apargs -x <PID> | grep AT_SUN_EXECNAME
...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 aswhereami()
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 definidoargv[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 pwdbin/executable
- relativo a pwd./foo
- relativo a pwdexecutable
- basename, encontre no caminhobin//executable
- relativo a pwd, não canônicosrc/../bin/executable
- relativo a pwd, não canônico, retornobin/./echoargc
- relativo a pwd, não canônicoValores que você não deve ver:
~/bin/executable
- reescrito antes que seu programa seja executado.~user/bin/executable
- reescrito antes da execução do programaalias
- 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 útilAlé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)
, edlgetname(...)
se estiver presente. Você pode até tentar essesargv[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()
oucanonicalize_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.cEsteja 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 deliberadamenteargv[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:
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:strcat()
estrcpy()
usarstrncat()
estrncpy()
. 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.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).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.
E aqui está o resultado que demonstra que, em todos os testes anteriores, ele realmente se encontrou.
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.fonte
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 quetarget
seja uma sequência vazia) sesource
for maior que o destino. O comprimento é o número de caracteres que podem ser anexados com segurança ao destino, excluindo o nulo à direita, assimsizeof(target)-1
como o máximo.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 .
fonte
Uma alternativa no Linux para usar
/proc/self/exe
ouargv[0]
está usando as informações transmitidas pelo intérprete ELF, disponibilizadas pela glibc como tal:Observe que
getauxval
é uma extensão glibc e, para ser robusto, você deve verificar se não retornaNULL
(indicando que o interpretador ELF não forneceu oAT_EXECFN
parâmetro), mas não acho que isso seja realmente um problema no Linux.fonte
Sim, isolar o código específico da plataforma
#ifdefs
é a maneira convencional de fazer isso.Outra abordagem seria ter um
#ifdef
cabeç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 .fonte
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.
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:fonte
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:/proc/self/exefile
existir, seu conteúdo será a informação solicitada./proc/<PID>/exefile
existir, seu conteúdo será a informação solicitada./proc/self/as
existir, então:open()
o arquivo.sizeof(procfs_debuginfo) + _POSIX_PATH_MAX
,.devctl(fd, DCMD_PROC_MAPDEBUG_BASE,...
.procfs_debuginfo*
.path
campo daprocfs_debuginfo
estrutura. Aviso : Por algum motivo, às vezes, o QNX omite a primeira barra/
do caminho do arquivo. Anexe isso/
quando necessário.3.
com o arquivo/proc/<PID>/as
.dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)
ondedlinfo
está umaDl_info
estrutura quedli_fname
pode conter as informações solicitadas.Eu espero que isso ajude.
fonte
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.
fonte
Você pode usar argv [0] e analisar a variável de ambiente PATH. Veja: Uma amostra de um programa que pode se encontrar
fonte
execv
e parentes tomar o caminho para o executável separadamente deargv
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.
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.
fonte
Se você usa C, pode usar a função getwd:
Isso imprimirá na saída padrão, o diretório atual do executável.
fonte
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.
fonte