问题
I'm trying to get the size in bytes of a /proc/pid/exe file with lstat. Here's my code:
int main(int argc, char *argv[])
{
struct stat sb;
char *linkname;
ssize_t r;
if (argc != 2)
{
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (lstat(argv[1], &sb) == -1)
{
perror("lstat");
exit(EXIT_FAILURE);
}
printf("sb.st_size %d\n", sb.st_size);
exit(EXIT_SUCCESS);
}
It seems like sb.st_size is ALWAYS equal to 0, and I don't understand why. Plus, this sample is extracted from readlink(2) man page.
Edit: I'm trying to get it working on openSUSE.
Thanks in advance for ur help people.
回答1:
The files in /proc are not ordinary files. For most of them, stat() et al. return .st_size == 0
.
In particular, /proc/PID/exe
is not really a symlink or a hardlink, but a special pseudofile, which behaves mostly like a symlink.
(If you need to, you can detect procfs files checking the .st_dev
field. Compare to .st_dev
obtained from lstat("/proc/self/exe",..)
, for example.)
To obtain the path to a specific execubtable based on its PID, I recommend an approach relying on the return value of readlink() instead:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* Creative Commons CC0: Public Domain dedication
* (In jurisdictions without public domain, this example program
* is licensed under the Creative Commons CC0 license.)
*
* To the extent possible under law, Nominal Animal has waived all
* copyright and related or neighboring rights to this example program.
*
* In other words, you are free to use it in any way you wish,
* but if it breaks something, you get to keep all the pieces.
*/
/** exe_of() - Obtain the executable path a process is running
* @pid: Process ID
* @sizeptr: If specified, the allocated size is saved here
* @lenptr: If specified, the path length is saved here
* Returns the dynamically allocated pointer to the path,
* or NULL with errno set if an error occurs.
*/
char *exe_of(const pid_t pid, size_t *const sizeptr, size_t *const lenptr)
{
char *exe_path = NULL;
size_t exe_size = 1024;
ssize_t exe_used;
char path_buf[64];
int path_len;
path_len = snprintf(path_buf, sizeof path_buf, "/proc/%ld/exe", (long)pid);
if (path_len < 1 || path_len >= sizeof path_buf) {
errno = ENOMEM;
return NULL;
}
while (1) {
exe_path = malloc(exe_size);
if (!exe_path) {
errno = ENOMEM;
return NULL;
}
exe_used = readlink(path_buf, exe_path, exe_size - 1);
if (exe_used == (ssize_t)-1)
return NULL;
if (exe_used < (ssize_t)1) {
/* Race condition? */
errno = ENOENT;
return NULL;
}
if (exe_used < (ssize_t)(exe_size - 1))
break;
free(exe_path);
exe_size += 1024;
}
/* Try reallocating the exe_path to minimum size.
* This is optional, and can even fail without
* any bad effects. */
{
char *temp;
temp = realloc(exe_path, exe_used + 1);
if (temp) {
exe_path = temp;
exe_size = exe_used + 1;
}
}
if (sizeptr)
*sizeptr = exe_size;
if (lenptr)
*lenptr = exe_used;
exe_path[exe_used] = '\0';
return exe_path;
}
int main(int argc, char *argv[])
{
int arg;
char *exe;
long pid;
char dummy;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
printf("\n");
printf("Usage: %s [ -h | --help ]\n", argv[0]);
printf(" %s PID [ PID ... ]\n", argv[0]);
printf("\n");
return 0;
}
for (arg = 1; arg < argc; arg++)
if (sscanf(argv[arg], " %ld %c", &pid, &dummy) == 1 && pid > 0L) {
exe = exe_of((pid_t)pid, NULL, NULL);
if (exe) {
printf("Process %ld runs '%s'.\n", pid, exe);
free(exe);
} else
printf("Process %ld: %s.\n", pid, strerror(errno));
} else {
printf("%s: Invalid PID.\n", argv[arg]);
return 1;
}
return 0;
}
Above, the exe_of()
function returns a dynamically allocated copy of where the pseudo-symlink /proc/PID/exe
points to, optionally storing the allocated size and/or the path length too. (The example program above doesn't need them, so they're NULL.)
The idea is very simple: Allocate an initial dynamic pointer that is large enough for most cases, but not ridiculously large. Reserve the last byte for the end-of-string NUL byte. If the size returned by readlink() is the same as the buffer length given to it -- it does not add a terminating end-of-string NUL byte itself --, then the buffer might have been too short; discard it, allocate a larger buffer, and retry.
Similarly, if you wish to read the full contents of a pseudo-file under /proc/
, you cannot use lstat()
/stat()
first to find out how large a buffer you might need; you need to allocate a buffer, read as much as you can, and when necessary, just reallocate a larger buffer. (I could show example code for that, too.)
Questions?
回答2:
Can't comment on the post I want to reply to so sorry!
Valgrind will report exe_path = malloc(exe_size);
in Nominal Animal's post as 'blocks are definitely lost'. So, if you're using that function then your memory will rise very quickly.
Freeing the memory even when you return NULL will fix this. Explicit casting of (char*) from the malloc calls should also be added to make gcc stop complaining when you use -Wall
char* exe_of(const pid_t pid, size_t *const sizeptr, size_t *const lenptr)
{
char *exe_path = NULL;
size_t exe_size = 1024;
ssize_t exe_used;
char path_buf[64];
unsigned int path_len;
path_len = snprintf(path_buf, sizeof path_buf, "/proc/%ld/exe", (long)pid);
if (path_len < 1 || path_len >= sizeof path_buf) {
errno = ENOMEM;
return NULL;
}
while (1) {
exe_path = (char*)malloc(exe_size);
if (!exe_path) {
errno = ENOMEM;
return NULL;
}
exe_used = readlink(path_buf, exe_path, exe_size - 1);
if (exe_used == (ssize_t)-1) {
free(exe_path);
return NULL;
}
if (exe_used < (ssize_t)1) {
/* Race condition? */
errno = ENOENT;
free(exe_path);
return NULL;
}
if (exe_used < (ssize_t)(exe_size - 1))
break;
free(exe_path);
exe_size += 1024;
}
/* Try reallocating the exe_path to minimum size.
* This is optional, and can even fail without
* any bad effects. */
{
char *temp;
temp = (char*)realloc(exe_path, exe_used + 1);
if (temp) {
exe_path = temp;
exe_size = exe_used + 1;
}
}
if (sizeptr)
*sizeptr = exe_size;
if (lenptr)
*lenptr = exe_used;
exe_path[exe_used] = '\0';
return exe_path;
}
回答3:
Generally you don't have to worry about this, but after commenting with Nominal Animal, it seems that linux limits filepaths from /proc/PID/exe by PAGE_SIZE. So even if the file system supports paths longer than that, there is no way to get readlink() to give you that path because this is implemented as a hard limit. I have found another way so IF readlink fails with ENAMETOOLONG, you can read /proc/PID/maps, although this could be hit or miss, i've found that it hits every process in 4 different distros EXCEPT system processes which do not have actual filenames (ENOENT)
int getExecutableFromMaps(char *buf, size_t bufsize) {
FILE *fp;
char *mylinebuf = NULL;
size_t mylinebufsize = 0;
size_t counter = 0;
size_t start = 0;
size_t column = 0;
int result = -1;
fp = fopen("/proc/self/maps", "r");
if( fp != NULL ) {
if( getline(&mylinebuf, &mylinebufsize, fp) >= 0 ) {
if( mylinebuf != NULL ) {
while( column < 5 && counter < mylinebufsize ) {
while( counter < mylinebufsize && mylinebuf[counter] != ' ') {
counter++;
}
while( counter < mylinebufsize && mylinebuf[counter] == ' ') {
counter++;
}
column++;
}
int start = counter;
while( counter < mylinebufsize && (mylinebuf[counter] != '\n' && mylinebuf[counter] != '\r') ) {
counter++;
}
if( counter <= mylinebufsize && start < counter && (counter-start+1)<=bufsize ) {
memcpy(buf, &mylinebuf[start], counter-start);
buf[counter-start+1] = 0;
result = counter-start+1;
}
}
}
if( mylinebuf != NULL ) {
free(mylinebuf);
}
fclose(fp);
}
return result;
}
Note: this is supplemental code that should only be called AFTER readlink fails, as this is expensive.
来源:https://stackoverflow.com/questions/24581908/c-lstat-on-proc-pid-exe