Tìm đường dẫn thực thi hiện tại mà không có / Proc / self / exe


190

Dường như với tôi rằng Linux dễ dàng với / Proc / self / exe. Nhưng tôi muốn biết liệu có cách nào thuận tiện để tìm thư mục của ứng dụng hiện tại trong C / C ++ với giao diện đa nền tảng hay không. Tôi đã thấy một số dự án lén lút xung quanh với argv [0], nhưng nó dường như không hoàn toàn đáng tin cậy.

Nếu bạn đã từng phải hỗ trợ, giả sử, Mac OS X, không có / Proc /, bạn sẽ làm gì? Sử dụng #ifdefs để cô lập mã dành riêng cho nền tảng (ví dụ NSBundle)? Hoặc cố gắng suy ra đường dẫn của tệp thực thi từ argv [0], $ PATH và không có gì, có nguy cơ tìm thấy lỗi trong các trường hợp cạnh?



Tôi googled: có được của tôi ps -o comm. Điều đưa tôi đến đây là: "/proc/pid/path/a.out"
lưu vực

Câu trả lời của IMHO tự hào xứng đáng được đặt lên hàng đầu, bởi vì nó giải quyết chính xác yêu cầu "giao diện đa nền tảng" và rất dễ tích hợp.
Stéphane Gourichon

Câu trả lời:


348

Một số giao diện dành riêng cho hệ điều hành:

Phương pháp di động (nhưng ít đáng tin cậy hơn) là sử dụng argv[0]. Mặc dù chương trình gọi có thể được đặt thành bất cứ thứ gì, theo quy ước, nó được đặt thành tên đường dẫn của tệp thực thi hoặc tên được tìm thấy bằng cách sử dụng $PATH.

Một số shell, bao gồm bash và ksh, đặt biến môi trường " _" thành đường dẫn đầy đủ của tệp thực thi trước khi nó được thực thi. Trong trường hợp đó bạn có thể sử dụng getenv("_")để có được nó. Tuy nhiên, điều này không đáng tin cậy vì không phải tất cả các shell đều thực hiện điều này và nó có thể được đặt thành bất cứ thứ gì hoặc bị bỏ lại từ quy trình cha mẹ không thay đổi nó trước khi thực hiện chương trình của bạn.


3
Và cũng lưu ý rằng _NSGetExecutablePath () không tuân theo các liên kết tượng trưng.
Naruse

1
NetBSD: readlink / Proc / curproc / exe DragonFly BSD: readlink / Proc / curproc / file
naruse

6
Solaris : char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));; khác với getexecname()- tương đương với pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.

4
"QĐesktopService :: StorageLocation (QĐesktopService :: DataLocation)" Đó không phải là đường dẫn thực thi, đó là tên đường dẫn của thư mục theo người dùng nơi lưu trữ dữ liệu.

2
OpenBSD là nơi duy nhất bạn vẫn không thể vào năm 2017. Bạn phải sử dụng cách PATH và argv [0]
Lothar

27

Việc sử dụng /proc/self/exelà không di động và không đáng tin cậy. Trên hệ thống Ubuntu 12.04 của tôi, bạn phải root để đọc / theo liên kết tượng trưng. Điều này sẽ làm cho ví dụ Boost và có thể các whereami()giải pháp được đăng không thành công.

Bài đăng này rất dài nhưng thảo luận về các vấn đề thực tế và trình bày mã thực sự hoạt động cùng với xác nhận đối với bộ kiểm tra.

Cách tốt nhất để tìm chương trình của bạn là truy xuất các bước tương tự mà hệ thống sử dụng. Điều này được thực hiện bằng cách sử dụng argv[0]giải quyết chống lại root hệ thống tập tin, pwd, môi trường đường dẫn và xem xét các liên kết tượng trưng, ​​và chuẩn hóa tên đường dẫn. Đây là từ bộ nhớ nhưng tôi đã thực hiện điều này trong quá khứ thành công và đã thử nghiệm nó trong nhiều tình huống khác nhau. Nó không được đảm bảo để hoạt động, nhưng nếu nó không có thể bạn có vấn đề lớn hơn nhiều và nó đáng tin cậy hơn bất kỳ phương pháp nào khác được thảo luận. Có một số tình huống trên một hệ thống tương thích Unix trong đó xử lý đúng cáchargv[0]sẽ không đưa bạn đến chương trình của bạn nhưng sau đó bạn đang thực hiện trong một môi trường bị phá vỡ chắc chắn. Nó cũng khá dễ mang theo cho tất cả các hệ thống có nguồn gốc Unix kể từ khoảng năm 1970 và thậm chí một số hệ thống có nguồn gốc không phải Unix vì về cơ bản nó phụ thuộc vào chức năng tiêu chuẩn libc () và chức năng dòng lệnh tiêu chuẩn. Nó nên hoạt động trên Linux (tất cả các phiên bản), Android, Chrome OS, Minix, Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, NeXTSTEP, vv Và với một chút thay đổi có lẽ VMS, VM / CMS, hệ điều hành DOS / Windows, ReactOS, OS / 2, vv Nếu một chương trình đã được đưa ra trực tiếp từ một môi trường GUI, nó cần phải có thiết lập argv[0]một đường dẫn tuyệt đối.

Hiểu rằng hầu hết mọi shell trên mọi hệ điều hành tương thích Unix đã được phát hành về cơ bản đều tìm thấy các chương trình theo cùng một cách và thiết lập môi trường hoạt động gần giống như vậy (với một số tính năng bổ sung tùy chọn). Và bất kỳ chương trình nào khác khởi chạy một chương trình dự kiến ​​sẽ tạo ra cùng một môi trường (argv, chuỗi môi trường, v.v.) cho chương trình đó như thể nó được chạy từ shell, với một số tính năng bổ sung tùy chọn. Một chương trình hoặc người dùng có thể thiết lập một môi trường lệch khỏi quy ước này cho các chương trình cấp dưới khác mà nó khởi chạy nhưng nếu có, đây là một lỗi và chương trình không có kỳ vọng hợp lý rằng chương trình cấp dưới hoặc cấp dưới của nó sẽ hoạt động chính xác.

Các giá trị có thể argv[0]bao gồm:

  • /path/to/executable - đường dẫn tuyệt đối
  • ../bin/executable - liên quan đến pwd
  • bin/executable - liên quan đến pwd
  • ./foo - liên quan đến pwd
  • executable - tên cơ sở, tìm trong đường dẫn
  • bin//executable - liên quan đến pwd, không chính tắc
  • src/../bin/executable - liên quan đến pwd, không chính tắc, quay lui
  • bin/./echoargc - liên quan đến pwd, không chính tắc

Các giá trị bạn không nên thấy:

  • ~/bin/executable - viết lại trước khi chương trình của bạn chạy.
  • ~user/bin/executable - viết lại trước khi chương trình của bạn chạy
  • alias - viết lại trước khi chương trình của bạn chạy
  • $shellvariable - viết lại trước khi chương trình của bạn chạy
  • *foo* - ký tự đại diện, viết lại trước khi chương trình của bạn chạy, không hữu ích lắm
  • ?foo? - ký tự đại diện, viết lại trước khi chương trình của bạn chạy, không hữu ích lắm

Ngoài ra, chúng có thể chứa các tên đường dẫn không chính tắc và nhiều lớp liên kết tượng trưng. Trong một số trường hợp, có thể có nhiều liên kết cứng đến cùng một chương trình. Ví dụ, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, vv có thể được liên kết cứng đến /bin/busybox.

Để tìm lại chính mình, hãy làm theo các bước dưới đây:

  • Lưu pwd, PATH và argv [0] khi vào chương trình của bạn (hoặc khởi tạo thư viện của bạn) vì chúng có thể thay đổi sau này.

  • Tùy chọn: đặc biệt đối với các hệ thống không phải Unix, tách riêng nhưng không loại bỏ phần tiền tố tên máy chủ / người dùng / ổ đĩa, nếu có; phần thường đi trước dấu hai chấm hoặc theo sau "//" ban đầu.

  • Nếu argv[0]là một đường dẫn tuyệt đối, sử dụng nó làm điểm bắt đầu. Một đường dẫn tuyệt đối có thể bắt đầu bằng "/" nhưng trên một số hệ thống không phải là Unix, nó có thể bắt đầu bằng "\" hoặc một ký tự tên hoặc tiền tố tên theo sau là dấu hai chấm.

  • Khác nếu argv[0]là một đường dẫn tương đối (chứa "/" hoặc "\" nhưng không bắt đầu với nó, chẳng hạn như "../../bin/foo", sau đó kết hợp pwd + "/" + argv [0] (sử dụng hiện tại thư mục làm việc từ khi chương trình bắt đầu, không hiện tại).

  • Khác nếu argv [0] là một tên cơ sở đơn giản (không có dấu gạch chéo), sau đó kết hợp nó với từng mục trong biến môi trường PATH lần lượt và thử những cái đó và sử dụng cái đầu tiên thành công.

  • Tùy chọn: Khác hãy thử chính nền tảng cụ thể /proc/self/exe, /proc/curproc/file(BSD) và (char *)getauxval(AT_EXECFN), dlgetname(...)nếu có. Bạn thậm chí có thể thử các argv[0]phương pháp dựa trên trước này , nếu chúng có sẵn và bạn không gặp phải vấn đề về quyền. Trong trường hợp hơi khó xảy ra (khi bạn xem xét tất cả các phiên bản của tất cả các hệ thống) mà chúng có mặt và không bị lỗi, chúng có thể có thẩm quyền hơn.

  • Tùy chọn: kiểm tra tên đường dẫn được truyền bằng cách sử dụng tham số dòng lệnh.

  • Tùy chọn: kiểm tra tên đường dẫn trong môi trường được truyền rõ ràng bằng tập lệnh trình bao bọc của bạn, nếu có.

  • Tùy chọn: Là giải pháp cuối cùng, hãy thử biến môi trường "_". Nó có thể trỏ đến một chương trình hoàn toàn khác, chẳng hạn như trình bao người dùng.

  • Giải quyết các liên kết tượng trưng, ​​có thể có nhiều lớp. Có khả năng các vòng lặp vô hạn, mặc dù nếu chúng tồn tại, chương trình của bạn có thể sẽ không được gọi.

  • Canonicalize tên tệp bằng cách giải quyết các chuỗi con như "/foo/../bar/" thành "/ bar /". Lưu ý điều này có khả năng thay đổi ý nghĩa nếu bạn vượt qua điểm gắn kết mạng, do đó, việc chuẩn hóa không phải lúc nào cũng tốt. Trên máy chủ mạng, ".." trong symlink có thể được sử dụng để đi qua một đường dẫn đến một tệp khác trong ngữ cảnh máy chủ thay vì trên máy khách. Trong trường hợp này, bạn có thể muốn bối cảnh máy khách để chuẩn hóa là ok. Đồng thời chuyển đổi các mẫu như "/./" thành "/" và "//" thành "/". Trong shell, readlink --canonicalizesẽ giải quyết nhiều liên kết tượng trưng và tên hợp quy. Chase có thể làm tương tự nhưng không được cài đặt. realpath()hoặc canonicalize_file_name(), nếu có, có thể giúp đỡ.

Nếu realpath()không tồn tại vào thời gian biên dịch, bạn có thể mượn một bản sao từ bản phân phối thư viện được cấp phép cho phép và tự biên dịch nó thay vì phát minh lại bánh xe. Khắc phục lỗi tràn bộ đệm tiềm năng (chuyển vào bộ đệm đầu ra sizeof, nghĩ rằng strncpy () so với strcpy ()) nếu bạn sẽ sử dụng bộ đệm ít hơn PATH_MAX. Có thể dễ dàng hơn chỉ sử dụng một bản sao riêng được đổi tên thay vì kiểm tra nếu nó tồn tại. Bản quyền giấy phép cho phép từ android / darwin / bsd: https://android.googlesource.com/pl platform / bionic / + / f077784 / libc / upstream-freebsd / lib / libc / stdlib / realpath.c

Xin lưu ý rằng nhiều lần thử có thể thành công hoặc thành công một phần và chúng có thể không chỉ ra cùng một thực thi, vì vậy hãy xem xét việc xác minh thực thi của bạn; tuy nhiên, bạn có thể không có quyền đọc - nếu bạn không thể đọc nó, đừng coi đó là một thất bại. Hoặc xác minh một cái gì đó gần với thực thi của bạn, chẳng hạn như thư mục "../lib/" mà bạn đang cố gắng tìm. Bạn có thể có nhiều phiên bản, phiên bản được đóng gói và biên dịch cục bộ, phiên bản cục bộ và mạng và phiên bản di động ổ đĩa cục bộ và USB, v.v. và có một khả năng nhỏ là bạn có thể nhận được hai kết quả không tương thích từ các phương pháp định vị khác nhau. Và "_" có thể chỉ đơn giản là trỏ đến chương trình sai.

Một chương trình sử dụng execvecó thể cố tình đặt argv[0]không tương thích với đường dẫn thực tế được sử dụng để tải chương trình và PATH bị hỏng, "_", pwd, v.v. mặc dù nhìn chung không có nhiều lý do để làm như vậy; nhưng điều này có thể có ý nghĩa bảo mật nếu bạn có mã dễ bị bỏ qua thực tế là môi trường thực thi của bạn có thể được thay đổi theo nhiều cách bao gồm, nhưng không giới hạn, đối với cách này (chroot, hệ thống tập tin cầu chì, liên kết cứng, v.v.) cho các lệnh shell để đặt PATH nhưng không xuất được nó.

Bạn không nhất thiết phải mã hóa cho các hệ thống không phải Unix nhưng sẽ là một ý tưởng tốt để nhận biết một số đặc thù để bạn có thể viết mã theo cách mà không khó để ai đó chuyển sang sau này . Xin lưu ý rằng một số hệ thống (DEC VMS, DOS, URL, v.v.) có thể có tên ổ đĩa hoặc các tiền tố khác kết thúc bằng dấu hai chấm như "C: \", "sys $ drive: [foo] bar" và "file : /// foo / bar / baz ". Các hệ thống DEC VMS cũ sử dụng "[" và "]" để đặt phần thư mục của đường dẫn mặc dù điều này có thể đã thay đổi nếu chương trình của bạn được biên dịch trong môi trường POSIX. Một số hệ thống, chẳng hạn như VMS, có thể có phiên bản tệp (được phân tách bằng dấu chấm phẩy ở cuối). Một số hệ thống sử dụng hai dấu gạch chéo liên tiếp như trong "// drive / path / to / file" hoặc "user @ host: / path / to / file" (lệnh scp) hoặc "file: (được phân cách bằng dấu cách) và "PATH" được phân cách bằng dấu hai chấm nhưng chương trình của bạn sẽ nhận được PATH vì vậy bạn không cần phải lo lắng về đường dẫn. DOS và một số hệ thống khác có thể có các đường dẫn tương đối bắt đầu bằng tiền tố ổ đĩa. C: foo.exe đề cập đến foo.exe trong thư mục hiện tại trên ổ C, vì vậy bạn cần phải tra cứu thư mục hiện tại trên C: và sử dụng nó cho pwd. (được phân cách bằng dấu cách) và "PATH" được phân cách bằng dấu hai chấm nhưng chương trình của bạn sẽ nhận được PATH vì vậy bạn không cần phải lo lắng về đường dẫn. DOS và một số hệ thống khác có thể có các đường dẫn tương đối bắt đầu bằng tiền tố ổ đĩa. C: foo.exe đề cập đến foo.exe trong thư mục hiện tại trên ổ C, vì vậy bạn cần phải tra cứu thư mục hiện tại trên C: và sử dụng nó cho pwd.

Một ví dụ về liên kết tượng trưng và trình bao bọc trên hệ thống của tôi:

/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

Lưu ý rằng hóa đơn người dùng đã đăng một liên kết ở trên lên một chương trình tại HP xử lý ba trường hợp cơ bản argv[0]. Nó cần một số thay đổi, mặc dù:

  • Nó sẽ là cần thiết để viết lại tất cả strcat()strcpy()để sử dụng strncat()strncpy(). Mặc dù các biến được khai báo có độ dài PATHMAX, giá trị đầu vào có độ dài PATHMAX-1 cộng với độ dài của chuỗi được nối là> PATHMAX và giá trị đầu vào của độ dài PATHMAX sẽ bị hủy bỏ.
  • Nó cần phải được viết lại như một chức năng của thư viện, thay vì chỉ in ra kết quả.
    • Nó không hợp lệ hóa tên (sử dụng mã realpath mà tôi đã liên kết ở trên)
    • Không thể giải quyết các liên kết tượng trưng (sử dụng mã realpath)

Vì vậy, nếu bạn kết hợp cả mã HP và mã realpath và sửa cả hai để có khả năng chống tràn bộ đệm, thì bạn nên có một cái gì đó có thể diễn giải đúng argv[0].

Phần sau đây minh họa các giá trị thực tế argv[0]cho nhiều cách gọi cùng một chương trình trên Ubuntu 12.04. Và vâng, chương trình đã vô tình được đặt tên là echoargc thay vì echoargv. Điều này đã được thực hiện bằng cách sử dụng tập lệnh để sao chép sạch nhưng thực hiện thủ công trong shell sẽ có kết quả tương tự (ngoại trừ bí danh không hoạt động trong tập lệnh trừ khi bạn bật chúng một cách rõ ràng).

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

Những ví dụ này minh họa rằng các kỹ thuật được mô tả trong bài viết này sẽ hoạt động trong một loạt các trường hợp và tại sao một số bước là cần thiết.

EDIT: Bây giờ, chương trình in argv [0] đã được cập nhật để thực sự tìm thấy chính nó.

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

Và đây là đầu ra chứng minh rằng trong mỗi một thử nghiệm trước đó, nó thực sự đã tìm thấy chính nó.

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"

Hai khởi chạy GUI được mô tả ở trên cũng tìm thấy chính xác chương trình.

Có một cạm bẫy tiềm năng. Các access()chức năng giảm quyền nếu chương trình được setuid trước khi thử nghiệm. Nếu có một tình huống mà chương trình có thể được tìm thấy như một người dùng nâng cao nhưng không phải là một người dùng thông thường, thì có thể có một tình huống mà các thử nghiệm này sẽ thất bại, mặc dù không chắc chương trình có thể được thực thi trong những trường hợp đó. Người ta có thể sử dụng euidaccess () thay thế. Tuy nhiên, có thể nó có thể tìm thấy một chương trình không thể truy cập sớm hơn trên đường dẫn so với người dùng thực tế có thể.


1
Bạn đặt rất nhiều nỗ lực vào đó - hoàn thành tốt. Thật không may, không strncpy()phải (đặc biệt) strncat()được sử dụng một cách an toàn trong mã. strncpy()không đảm bảo chấm dứt null; nếu chuỗi nguồn dài hơn không gian đích, chuỗi không bị hủy kết thúc. strncat()rất khó sử dụng; strncat(target, source, sizeof(target))là sai (ngay cả khi targetlà một chuỗi trống để bắt đầu) nếu sourcedài hơn mục tiêu. Độ dài là số lượng ký tự có thể được thêm một cách an toàn vào mục tiêu không bao gồm null null, vì vậy sizeof(target)-1là tối đa.
Jonathan Leffler

4
Mã strncpy là chính xác, không giống như phương pháp bạn ngụ ý tôi nên sử dụng. Tôi đề nghị bạn đọc mã cẩn thận hơn. Nó không tràn bộ đệm cũng không khiến chúng bị hủy hoại. Mỗi lần sử dụng strncpy () / stncat () bị giới hạn bởi sao chép sizeof (bộ đệm), hợp lệ, và sau đó ký tự cuối cùng của bộ đệm được điền bằng 0 ghi đè ký tự cuối cùng của bộ đệm. Tuy nhiên, strncat () sử dụng tham số kích thước không chính xác như một số đếm và có thể tràn do thực tế là nó có trước các cuộc tấn công tràn bộ đệm.
viêm màng não

"sudo apt-get install libbsd0 libbsd-dev", sau đó s / strncat / strlcat /
wh viêm

1
Không sử dụng PATH_MAX. Điều này đã ngừng hoạt động 30 năm trước, luôn luôn sử dụng malloc.
Lothar

Ngoài ra nếu bạn sử dụng một cuộc gọi init. Giải quyết hoàn toàn đường dẫn đến exe trên init và không chỉ là một phần và sau đó thực hiện nó sau cuộc gọi. Không có đánh giá lười biếng ở đây có thể nếu bạn sử dụng realpath trong trình giải quyết. Cùng với các erorr khác chỉ đơn giản là mã tồi tệ nhất tôi từng thấy trên stackoverflow trong một câu trả lời dài.
Lothar

13

Kiểm tra thư viện whereeami từ Gregory Pakosz (chỉ có một tệp C); nó cho phép bạn có được đường dẫn đầy đủ đến tệp thực thi hiện tại trên nhiều nền tảng khác nhau. Hiện tại, nó có sẵn dưới dạng repo trên github tại đây .


8

Một cách khác trên Linux để sử dụng /proc/self/exehoặc argv[0]là sử dụng thông tin được thông qua bởi trình thông dịch ELF, được cung cấp bởi glibc như sau:

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

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

Lưu ý rằng đó getauxvallà một tiện ích mở rộng glibc và để mạnh mẽ, bạn nên kiểm tra để nó không quay trở lại NULL(chỉ ra rằng trình thông dịch ELF chưa cung cấp AT_EXECFNtham số), nhưng tôi không nghĩ đây thực sự là một vấn đề trên Linux.


Tôi thích điều này vì đơn giản và glibc được bao gồm trong Gtk + (mà tôi đang sử dụng).
Colin Keenan

4

Nếu bạn đã từng phải hỗ trợ, giả sử, Mac OS X, không có / Proc /, bạn sẽ làm gì? Sử dụng #ifdefs để cô lập mã dành riêng cho nền tảng (ví dụ NSBundle)?

Có cách ly mã cụ thể nền tảng với #ifdefslà cách thông thường được thực hiện.

Một cách tiếp cận khác là có một #ifdeftiêu đề sạch không có chứa các khai báo hàm và đặt các cài đặt trong các tệp nguồn cụ thể của nền tảng. Ví dụ, kiểm tra thư viện Poco C ++ làm điều gì đó tương tự cho lớp Môi trường của họ .


4

Làm cho công việc này đáng tin cậy trên các nền tảng yêu cầu sử dụng các câu lệnh #ifdef.

Đoạn mã dưới đây tìm thấy đường dẫn của tệp thực thi trong Windows, Linux, MacOS, Solaris hoặc FreeBSD (mặc dù FreeBSD chưa được kiểm tra). Nó sử dụng boost > = 1.55.0 để đơn giản hóa mã nhưng nó đủ dễ để loại bỏ nếu bạn muốn. Chỉ cần sử dụng định nghĩa như _MSC_VER và __linux là hệ điều hành và trình biên dịch yêu cầu.

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

Phiên bản trên trả về các đường dẫn đầy đủ bao gồm tên thực thi. Nếu thay vào đó, bạn muốn đường dẫn không có tên thực thi #include boost/filesystem.hpp>và thay đổi câu lệnh return thành:

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

@Frank, không chắc tại sao bạn lại nói thế. Làm việc cho tôi. Tôi thấy một phản hồi khác khẳng định bạn cần root để truy cập / Proc / self / exe nhưng tôi không thấy rằng trên bất kỳ hệ thống Linux nào tôi đã thử (CentOS hoặc Mint).
jtbr

2

Tùy thuộc vào phiên bản của QNX Neutrino , có nhiều cách khác nhau để tìm đường dẫn và tên đầy đủ của tệp thực thi được sử dụng để bắt đầu quá trình chạy. Tôi biểu thị định danh quá trình là <PID>. Hãy thử như sau:

  1. Nếu tập tin /proc/self/exefiletồn tại, thì nội dung của nó là thông tin được yêu cầu.
  2. Nếu tập tin /proc/<PID>/exefiletồn tại, thì nội dung của nó là thông tin được yêu cầu.
  3. Nếu tệp /proc/self/astồn tại, thì:
    1. open() tập tin.
    2. Phân bổ một bộ đệm của, ít nhất , sizeof(procfs_debuginfo) + _POSIX_PATH_MAX.
    3. Cho bộ đệm đó làm đầu vào devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Truyền bộ đệm cho a procfs_debuginfo*.
    5. Các thông tin được yêu cầu là tại pathlĩnh vực của procfs_debuginfocấu trúc. Cảnh báo : Vì một số lý do, đôi khi, QNX bỏ qua dấu gạch chéo đầu tiên /của đường dẫn tệp. Chuẩn bị rằng /khi cần thiết.
    6. Dọn dẹp (đóng tệp, giải phóng bộ đệm, v.v.).
  4. Hãy thử các thủ tục 3.với tập tin /proc/<PID>/as.
  5. Hãy thử dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)dlinfolà một Dl_infocấu trúc có dli_fnamethể chứa các thông tin yêu cầu.

Tôi hi vọng cái này giúp được.


1

AFAIK, không có cách nào như vậy. Và cũng có một câu trả lời: bạn muốn lấy câu trả lời là gì nếu cùng một tệp thực thi có nhiều liên kết cứng "trỏ" đến nó? (Liên kết cứng không thực sự "trỏ", chúng cùng một tệp, chỉ ở một nơi khác trong hệ thống phân cấp của FS.) Sau khi thực hiện thành công () thực hiện thành công một nhị phân mới, tất cả thông tin về các đối số của nó sẽ bị mất.


1
"Một khi execve () thực hiện thành công một nhị phân mới, tất cả thông tin về các đối số của nó sẽ bị mất." Trên thực tế, các đối số argp và envp không bị mất, chúng được truyền dưới dạng argv [] và môi trường và, trong một số UN * Xes, đối số tên đường dẫn hoặc một cái gì đó được xây dựng từ nó được truyền cùng với argp và envp (OS X / iOS, Solaris) hoặc được cung cấp thông qua một trong các cơ chế được liệt kê trong câu trả lời của mark4o. Nhưng, vâng, điều đó chỉ cung cấp cho bạn một trong những liên kết cứng nếu có nhiều hơn một liên kết.

1

Bạn có thể sử dụng argv [0] và phân tích biến môi trường PATH. Nhìn vào: Một mẫu của một chương trình có thể tự tìm thấy


7
Điều này thực sự không đáng tin cậy (mặc dù nó thường sẽ hoạt động với các chương trình được khởi chạy bởi các shell thông thường), bởi vì execvvà kin đi theo con đường thực thi riêng biệt củaargv
dmckee --- ex-moderator kitten

9
Đây là một câu trả lời không chính xác. Nó có thể cho bạn biết nơi bạn có thể tìm thấy một chương trình có cùng tên. Nhưng nó không cho bạn biết bất cứ điều gì về nơi thực sự đang chạy thực sự sống.
Larry Gritz

0

Cách di động hơn để có được tên đường dẫn của hình ảnh thực thi:

ps có thể cung cấp cho bạn đường dẫn của tệp thực thi, với điều kiện bạn có id tiến trình. Ngoài ra ps là một tiện ích POSIX nên nó có thể mang theo được

vì vậy nếu id quá trình là 249297 thì lệnh này chỉ cung cấp cho bạn tên đường dẫn.

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

Giải thích về lập luận

-p - chọn quy trình đã cho

-o comm - hiển thị tên lệnh (-o cmd chọn toàn bộ dòng lệnh)

--no-title - không hiển thị dòng tiêu đề, chỉ đầu ra.

Chương trình AC có thể chạy này thông qua popen.


Nó cung cấp đầy đủ chuỗi khởi động với params.
ETech

--no-title là không di động
Người tốt

1
không hoạt động nếu đối số đầu tiên để execv không phải là một đường dẫn tuyệt đối.
hroptatyr 7/07/2015

-4

Nếu bạn sử dụng C, bạn có thể sử dụng chức năng getwd:

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

Điều này bạn sẽ in trên đầu ra tiêu chuẩn, thư mục hiện tại của tệp thực thi.


3
ít nhất là trên Windows, thư mục làm việc hiện tại không có liên quan cụ thể đến việc chạy thực thi. Ví dụ: CreatProcess có thể khởi chạy một .exe và đặt thư mục làm việc của nó hoàn toàn độc lập.
Spike0xff

Tình huống là giống nhau trên mọi HĐH khác: thư mục hiện tại đôi khi giống với thư mục thực thi bởi tình huống, nhưng có thể hoàn toàn khác nhau.
Lassi

-10

Đường dẫn giá trị tuyệt đối của một chương trình nằm trong PWD của envp của hàm chính của bạn, cũng có một hàm trong C được gọi là getenv, vì vậy có.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.