C - Lstat on /proc/pid/exe

c, opensuse, symlink, system-calls

Solution

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?

Problem

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.

Original source

Related problems