Wyświetlenie ścieżki programu bez nawy pliku

0

Czy ten program który napisałem jest poprawny? Jak można by go bardziej skrócić?

#include <iostream>
#include <windows.h>

using namespace std;

int main(){
	char filename[MAX_PATH];	
	GetModuleFileName(NULL, filename, MAX_PATH);
	const BYTE* lpData = (const BYTE*)filename;
	
	string _file(filename), folder, folder_;

	for (int i = _file.size(); i > -1; --i)
		if (_file[i] == 0x5C)
			for(i; i > -1; --i)
				folder += _file[i];

	for (int i = fol.size()-1; i > -1; --i)
		folder_ += folder[i];
	
	cout<<folder_;
}
1
#include <string>
#include <iostream>
using namespace std;
 
int main(int argc,char *argv[])
  {
   string path(argv[0]);
   path.erase(path.find_last_of("\\/"));
   cout<<path<<endl;
   return 0;
  }

Ba, zadziała pod linuksem również.

1

Ten kod zadziała tylko jeśli program jest uruchomiony z ścieżka lub poprzez dwuklik ale jeśli się uruchomi go poprzez podanie nazwy to program się wykrzaczy dlatego wolałbym kod bez argv[0] :)

user image

0

Dzięki za przykładowy kod udało mi się już poprawić swój :)

user image

0

ale jeśli się uruchomi go poprzez podanie nazwy
Poprzez co? :|

1
Patryk27 napisał(a):

ale jeśli się uruchomi go poprzez podanie nazwy
Poprzez co? :|

Tutaj chodzi, że jeśli wpiszę tylko samą nazwę pliku w wierszu poleceń to program przestaje odpowiadać i nie działa jak należy.

1

Problem w tym że GetModuleFileName ogranicza cię tylko do windows.
Więc zawsze możesz zaradzić w ten sposób:

#include <string>
#include <iostream>
using namespace std;
 
int main(int argc,char *argv[])
  {
   string path(argv[0]);
   size_t pos=path.find_last_of("\\/");
   if(pos==string::npos) path=".";
   else path.erase(pos);
   cout<<path<<endl;
   return 0;
  }

Ewentualnie:

#include <string>
#include <iostream>
#include <windows.h>
using namespace std;
 
int main(int argc,char *argv[])
  {
   string path(MAX_PATH+1,'\0');
   path.resize(GetModuleFileName(0,&path[0],MAX_PATH));
   path.erase(path.find_last_of("\\/"));
   cout<<path<<endl;
   return 0;
  }
0

Wersja z obsluga nazwy bez sciezki:

#include <string>
#include <iostream>
using namespace std;
 
int main(int argc,char *argv[])
  {
   string path(argv[0]);
   size_t pos = path.find_last_of("\\/");
   if (pos != string::npos)
     path.erase(pos);
   else
     path = "";
   cout<<path<<endl;
   return 0;
  }
0

Ok. Dzięki za pomoc :)

34

Dopiszę kilka rzeczy, bo widzę, że jest parę nie do końca poprawnych przekonań napisanych wyżej ;)

_13th_Dragon napisał(a)

Jak brak ścieżki w argv to ścieżką jest ścieżka bieżąca, czyli pod windows: ".\" pod linuxem "./" a że zwracamy bez końcowej łamanej to jest bez końcowej czyli sama kropka.

To nie jest niestety prawdziwe stwierdzenie - plik wykonywalny może być równie dobrze w dowolnym miejscu wskazanym przez zmienną środowiskową PATH. Dla przykładu załóżmy, że mamy następujący kod:

#include <stdio.h>

int main(int argc, char **argv) {
  int i;

  printf("argc: %i\n", argc);
  for(i = 0; i < argc; i++) {
    printf("argv[%i]: %s\n", i, argv[i]);
  }

  return 0;
}

Uruchomienie z cwd i katalogu w PATH pod Windows:

gynvael:haven-windows> test.exe
argc: 1
argv[0]: test.exe

gynvael:haven-windows> mkdir asdf

gynvael:haven-windows> move test.exe asdf
        1 file(s) moved.

gynvael:haven-windows> set path=asdf;%path%

gynvael:haven-windows> test
argc: 1
argv[0]: test

Jak widać argv[0] nie zawiera ścieżki. Co więcej, argv[0] nie zawiera również rozszerzenia.
Ah, i jak widać wrzuciłem sobie do PATH ścieżkę relatywną - zazwyczaj powinno się wrzucać bezwzględną, ale na potrzeby eksperymentu to wystarczy.

A teraz to samo pod Ubuntu (przy czym zostawiłem nazwę exeka "a.out', ponieważ bash posiada wbudowane polecenie o nazwie test, a jeśli się nie poda ścieżki, to przez bash preferowane jest polecenie wbudowane):

10:30:56 gynvael:haven-linux> gcc test.c
10:31:01 gynvael:haven-linux> ./a.out 
argc: 1
argv[0]: ./a.out
10:31:03 gynvael:haven-linux> mv a.out asdf
10:31:06 gynvael:haven-linux> export PATH=asdf:$PATH
10:31:18 gynvael:haven-linux> a.out
argc: 1
argv[0]: a.out

Aby trochę sprecyzować, argv[0] niekoniecznie musi zawierać ścieżkę do (czy nawet pełną nazwę) exeka; argv[0] zawiera informacje o tym jak program został wywołany.

Kilka losowych przykładów jeszcze.

  1. Linki symboliczne:
10:34:37 gynvael:haven-linux> ln -s ./asdf/a.out frrr
10:34:49 gynvael:haven-linux> ./frrr
argc: 1
argv[0]: ./frrr
10:34:55 gynvael:haven-linux> ls -la frrr
lrwxrwxrwx 1 gynvael gynvael 12 Mar  9 10:34 frrr -> ./asdf/a.out

Exek się oczywiście nie nazywa "frrr", ale tak został wywołany, więc taką informację Bash przekazał do procesu potomnego.
Z tego zachowania korzysta trochę poleceń pod Unixami btw, które mają jeden plik wykonywalny, ale zachowują się różnie w zależności od tego przez który link zostały wywołane (idealnym przykładem jest busybox).

  1. Nic-co-by-było-związane-z-czymkolwiek-sensownym w argv[0]
    Pod Ubuntu zawartość argv[0] całkowicie kontroluje proces-rodzic, więc nie musi tam być absolutnie nic związanego z tym co jest wywoływane. Rozważmy następujący kod:
#include <unistd.h>

int main(int argc, char **argv, char **envp) {
  char * new_argv[] = {
    "ala ma kota kot ma ale", // argv[0]
    NULL
  };

  execve("/tmp/asdf/a.out", new_argv, envp);

  return 0;
}

EDIT
Mieliśmy z @KrzaQ krótką dyskusje o tym czemu zrobiłem coś tak głupiego jak przypisanie literału tekstowego do char*, więc krótkie wyjaśnienie: jak najbardziej zdaje sobie sprawę, że w C domyślnie literały są const i kompilator może je wrzucić do pamięci read-only (co więcej, jak @KrzaQ wskazał, kompilator może różne literały połączyć w pamięci w jeden - np. zamiast osobno wrzucać "foobar" i "bar", wrzucić tylko "foobar", bo ten zawiera "bar" w sobie). Natomiast tutaj jest jeszcze jeden czynnik - execve z losowych powodów wymaga char* a nie const char*, mimo, iż jest gwarancja, iż char* nigdy nie zostanie zmodyfikowany (mowa cały czas o pojedynczym elemencie argv[]) - vide http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html . Ja natomiast byłem trochę leniwy i nie chciało mi się w kodzie wyżej robić rzutowania, więc od razu zrobiłem char* - to przejdzie w przypadku eksperymentu, ale w kodzie produkcyjnym ofc lepiej tak nie robić. Przy czym, jeszcze trzy rzeczy:

  • "writable string literals" jest wymienione w standardzie C11 jako "common extension", więc niektóre kompilatory mogą domyślnie robić zapisywalne literały, ew. może być opcja by takie zrobić (np. @KrzaQ wskazał -fwritable-strings z GCC; w dokumentacji jest fajny dopisek btw: "Writing into string constants is a very bad idea; "constants" should be constant.")
  • może się okazać, że kompilator (przy normalnych zachowaniu) wrzuci string literal do sekcji .rdata (read-only data), która z założenia jest tylko do odczytu, ale, z uwagi na układ sekcji, .rdata trafi do pamięci mimo wszystko zapisywalnej (bardzo rzadkie, ale chyba widziałem coś takiego) albo wykonywalnej - to akurat dość częste w przypadku GCC na Ubuntu.
  • nie wiem jak jest teraz, ale jeszcze parę lat temu jak się spakowało exeka UPXem, to ten zmieniał wszystkie rzeczy tylko-do-odczytu na read-write-execute, co bardzo ułatwiało exploitacje ;)
    KONIEC EDIT

Kompilacja i uruchomienie:

10:36:58 gynvael:haven-linux> gcc run.c 
10:38:29 gynvael:haven-linux> ./a.out 
argc: 1
argv[0]: ala ma kota kot ma ale

Co więcej, pod Windowsem jest dokładnie tak samo ;)

#include <windows.h>

int main(void) {
  PROCESS_INFORMATION pi;
  STARTUPINFO si = { sizeof(STARTUPINFO) };

  CreateProcess(
      "asdf\\test.exe",
      "ala ma kota kot ma ale", // Command line
      NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

  WaitForSingleObject(pi.hProcess, 1000);
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);  
  return 0;
}
gynvael:haven-windows> gcc runwin.c -o runwin

gynvael:haven-windows> runwin
argc: 6
argv[0]: ala
argv[1]: ma
argv[2]: kota
argv[3]: kot
argv[4]: ma
argv[5]: ale

Ciekawostka: A magia to się zaczyna dopiero dziać, jak się da "*" jako command line ;)

gynvael:haven-windows> runwin
argc: 5
argv[0]: .runwin.c.swp
argv[1]: asdf
argv[2]: runwin.c
argv[3]: runwin.exe
argv[4]: test.c

Przy czym to akurat specyfika domyślnego zachowania prologu kompilatora MinGW, która rozwija symbole wieloznaczne (*, ?) w argv[] (łącznie z argv[0]) do listy plików spełniających określony pattern (jeśli ktoś ma moją książkę, to coś więcej napisałem o tym na stronie 340 w ramce "Argumenty main i rozwinięcie symboli wieloznacznych [VERBOSE]"; a jeśli ktoś nie ma, to szybkie info: aby to wyłączyć w MinGW trzeba w programie mieć zmienną globalną int _CRT_glob=0, natomiast aby to włączyć w MSVC, trzea zlinkować z plikiem setargv.obj lub wsetargv.obj).

W ramach ciekawostki jeszcze dodam, że MAXPATH (~259 znaków) wystarczy w 99.999% przypadków, natomiast pod Windowsem ścieżki mogą mieć do 32K znaków (vide http://www.icewall.pl/?p=467).

Na zakończenie jeszcze info jak pobrać nazwę i ścieżkę pliku wykonywalnego pod Ubuntu:

#include <stdio.h>
#include <unistd.h>

int main(void) {
  // Readlink nie dodaje \0, wiec buf powinien byc wyzerowany.
  char exe_name[4096] = {0};  
  readlink("/proc/self/exe", exe_name, sizeof(exe_name) - 1);

  printf("exe: %s\n", exe_name);

  return 0;
}
10:58:46 gynvael:haven-linux> gcc getname.c 
10:58:49 gynvael:haven-linux> ./a.out 
exe: /tmp/a.out

I jeszcze jedna ciekawostka (kudos jagger) - /proc/self/exe nie do końca jest linkiem ;)
Okazuje się, że nawet jeśli usuniemy plik wykonywalny (a proces nadal będzie chodzić ofc), to po /proc/self/exe nadal dostaniemy się do pliku wykonywalnego, pomimo tego, iż ls będzie twierdzić, że docelowy plik został usunięty. Np.

11:01:24 gynvael:haven-linux> cp /bin/cat /tmp/xxx
11:01:29 gynvael:haven-linux> ./xxx
^Z
[1]+  Stopped                 ./xxx

11:01:39 gynvael:haven-linux> rm xxx

11:01:46 gynvael:haven-linux> ls -la xxx
ls: cannot access xxx: No such file or directory

11:01:54 gynvael:haven-linux> ps aux | grep xxx | grep -v grep
gynvael  19605  0.0  0.0   7212   360 pts/13   T    11:01   0:00 ./xxx

11:02:03 gynvael:haven-linux> ls -la /proc/19605/exe
lrwxrwxrwx 1 gynvael gynvael 0 Mar  9 11:02 /proc/19605/exe -> /tmp/xxx (deleted)

11:02:14 gynvael:haven-linux> cat /proc/19605/exe | hexdump -C | head
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  02 26 40 00 00 00 00 00  |..>......&@.....|
00000020  40 00 00 00 00 00 00 00  20 b4 00 00 00 00 00 00  |@....... .......|
00000030  00 00 00 00 40 00 38 00  09 00 40 00 1c 00 1b 00  |[email protected]...@.....|
00000040  06 00 00 00 05 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000050  40 00 40 00 00 00 00 00  40 00 40 00 00 00 00 00  |@.@.....@.@.....|
00000060  f8 01 00 00 00 00 00 00  f8 01 00 00 00 00 00 00  |................|
00000070  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  |................|
00000080  38 02 00 00 00 00 00 00  38 02 40 00 00 00 00 00  |8.......8.@.....|
00000090  38 02 40 00 00 00 00 00  1c 00 00 00 00 00 00 00  |8.@.............|

11:02:35 gynvael:haven-linux> killall -9 xxx
[1]+  Killed                  ./xxx

11:02:46 gynvael:haven-linux> 

Nyom.

0

No dobra, skoro już piszemy jak to się robi na prawdę, to dodam coś od siebie:

/// Returns executive file path for the specified process
std::string GetExePath(DWORD dwProcID)
{
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,dwProcID);
	MODULEENTRY32 me;
	std::string path;
	me.dwSize=sizeof(me);

	if (Module32First(hSnap,&me))
		path = me.szExePath;

	CloseHandle(hSnap);
	return path;
}

Nie pytajcie skąd to mam, bo nie pamiętam i nie chce mi się szukać.
Czy działa zawsze - to raczej wątpliwe.
Oczywiście Win32 API.

1 użytkowników online, w tym zalogowanych: 0, gości: 1