Amina Amina - 3 months ago 17
C Question

C - Lstat on /proc/pid/exe

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.

Answer

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>

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

Comments