C++ equivalent of 'readlink -f'

1.5k Views Asked by At

In unistd.h, I have the C function readlink, which follows a link down one target. The coreutil readlink has the great -f option to follow every sym link recursively. How can I get this behavior in C++? A boost library, a posix function I don't know about, etc?

** Edit ** was just looking at the man page for realpath. Is this giving the same 'canonical' expansion of readlink -f?

3

There are 3 best solutions below

0
On

I needed to do exactly this today and the suggestion to use realpath() failed because the symlink was relative! If you are using glibc, consider canonicalize_file_name (https://man7.org/linux/man-pages/man3/canonicalize_file_name.3.html).

Of course, I found that after I wrote this code. I don't know how cross-platform this is. I wouldn't recommend it if canonicalize_file_name is available for you, but it might be a good starting point :)

char *readlink_f(const char *path) {
    struct stat sb;
    char *linkname = NULL;
    ssize_t r;
    int ret = -1;
    char *ptmp = NULL;
    char *dir; // not allocated
    char *relpath = NULL;
    char *abspath = NULL;

    /* create enough space to read the link into */
    if (lstat(path, &sb) == -1) {
        fprintf(stderr, "failed to lstat the path\n");
        goto error;
    }
    linkname = malloc(sb.st_size + 1);
    if (linkname == NULL) {
        fprintf(stderr, "insufficient memory\n");
        goto error;
    }
    r = readlink(path, linkname, sb.st_size + 1);
    if (r < 0) {
        fprintf(stderr, "failed to readlink the path\n");
        goto error;
    }
    if (r > sb.st_size) {
        fprintf(stderr, "symlink increased in size between lstat() and readlink()\n");
        goto error;
    }
    linkname[sb.st_size] = '\0';

    if (linkname[0] != '/') {
        /* realpath fails with relative symlinks */
        ptmp = strdup(path); /* dirname modifies its argument */
        dir = dirname(ptmp);
        if (asprintf(&relpath, "%s/%s", dir, linkname) == -1) {
            fprintf(stderr, "failed to get generate absolute path\n");
            goto error;
        }
    } else {
        relpath = strdup(linkname);
    }

    /* canonicalize the path */
    abspath = realpath(relpath, NULL);
    if (!abspath) {
        fprintf(stderr, "failed to get canonical path\n");
        goto error;
    }

    goto cleanup;

error:
    free(abspath);
    abspath = NULL;

cleanup:
    free(linkname);
    free(ptmp);
    free(relpath);
    return abspath;
}
0
On

Yes, realpath is the equivalent of readlink -f.

See the man page for more information

0
On

In C++17 you can use std::filesystem::canonical (or weakly_canonical if the file doesn't have to exist). It is also cross-platform. If you can't use C++17 yet, you may still be able to use the std::filesystem::experimental version.