0

I am trying to get the path which contains the executable of a PID from /proc/pid/cmdline in C. The man page states:

"The command-line arguments appear in this file as a set of strings separated by null bytes ('\0'), with a further null byte after the last string."

My idea (pseudocode):

int main(int argc, char** argv){
    // Assume file_path has been initialized
    char executable_path[1000];
    FILE* file = fopen(file_path, "r");
    if(f != NULL){

        fscanf("%s", executable_path);
    }
    return 0;
}

Since I only want the first string from this file(since that is the path containing the executable) and I know for sure that there is a '\0' after the first string, would fscanf be the correct function to use? Will it detect the first '\0' and then store the whole string up to the first '\0' in the executable_path array? (Note: I don't need to extract any of the other strings).

Thanks!

Bill
  • 11
  • 1
  • 2
  • Your question contains code did you try it? What was the result? My hunch (I haven't checked the docs) is that scanf will not terminate on \0 for %s. It does only terminate on whitespace if I remember correctly. – visibleman Feb 14 '18 at 02:53
  • You can easily adapt the `get_argv()` function in [this answer](https://stackoverflow.com/a/28030139/1475978), which reads `/proc/self/cmdline`, to read `/proc/PID/cmdline` instead. However, if you are interested in the executable only, then use [`readlink()`](http://man7.org/linux/man-pages/man2/readlink.2.html) on `/proc/PID/exe` instead. – Nominal Animal Feb 14 '18 at 03:26
  • @NominalAnimal I am not allowed to use readlink() or any system calls. If there are arguments following the path, how would I go about getting the path only? – Bill Feb 14 '18 at 03:41
  • @Bill: `readlink()` is not a system call, it's a standard POSIX.1 C function, which is used to obtain the path a symlink points to. – Nominal Animal Feb 16 '18 at 21:02

2 Answers2

2

Don't use fscanf for this, if the command name has a space in it, you won't get the whole name.

You can use fgets, it will stop reading at the '\0'. I have right now a process running with pid 10979:

FILE *fp = fopen("/proc/10979/cmdline", "r");

char line[1024];
fgets(line, sizeof line, fp);

puts(line);

fclose(fp);

This prints:

/usr/bin/gvim
Pablo
  • 13,271
  • 4
  • 39
  • 59
  • I put your path in a cmdline file I created for testing along with some arguments : "/usr/bin/gvim arg1 arg2 arg3". However, the code prints the entire string and does not print "/usr/bin/gvim". – Bill Feb 14 '18 at 03:26
  • @Bill it will show just what is executed ... so if you execute giving full path to gvim it will show full path ... its instructive to issue a directory listing on your proc in question from a terminal to view alternative attributes other than cmdline ... as per ls -la /proc/10979 ... here you will see ls -la /proc/10979/exe ... unless there is a shortcut to obtain the full path to cmd I would say you can resolve it by first obtaining the PATH of the pid to then discover the cmd location ... UPDATE checkout this readlink -f /proc/15870/exe which gives you full path – Scott Stensland Feb 14 '18 at 04:23
  • @ScottStensland Would fread detect the first null terminator as well? – Bill Feb 14 '18 at 05:49
  • @Bill probably yes, I haven't tested it. But you can test it. --- *I put your path in a cmdline file I created for testing along with some arguments* where did you put that? Note that this only works because `/proc//cmdline` uses `'\0'` as separator. – Pablo Feb 14 '18 at 09:51
0
  1. /proc/PID/cmdline contains the command line used to execute the program (unless modified by the program itself). It does not contain the path to the executable; it only contains the path used to execute it.

    For example, if you execute foo bar baz, its /proc/PID/cmdline will contain foo\0bar\0baz\0.

    You can use e.g. getdelim() with '\0' as the delimiter to read the path to the executable.
     

  2. To get the actual path of the executable, examine the /proc/PID/exe symlink. It will always point to the executable.

    To read the symlink, you can use the POSIX.1 readlink() function provided by the C library.

    I have an example implementation in this answer.

    In Linux, there is a kernel "bug": if an executable is very deep in the filesystem hierarchy, the readlink() call on the /proc/PID/exe pseudo-symlink fails with ENAMETOOLONG error.
     

  3. The path to the executable can also be extracted from /proc/PID/stat.

    The name of the executable is between the first ( and last ) in that pseudo-file.

    You can use getline() to read the entire line (entire pseudofile, actually, because it only contains a newline at end), strchr() to locate the first ( in it, and strrchr() to locate the last ).
     

If you want to experiment with funny/nasty executable names, just run e.g.

ln -s /bin/cat $'foo)\nbar\n('
$'./foo)\nbar\n(' /proc/self/stat
rm $'foo)\nbar\n('

in Bash, and examine the contents of /proc/self/stat.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86