4

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?

pythonic metaphor
  • 10,296
  • 18
  • 68
  • 110
  • 1
    https://stackoverflow.com/questions/8417976/resolve-symlinks-with-boost-filesystem – Vadim Feb 04 '14 at 19:21
  • 1
    You could try stracing readlink -f and then see what it does there – PlasmaHH Feb 04 '14 at 19:23
  • @PlasmaHH: You could also look at the source; `readlink` is part of GNU Coreutils. I suspect it just repeatedly invokes the `readlink` system call; it would be easy enough to do the same in your own program. – Keith Thompson Feb 04 '14 at 19:25
  • @Vadim looking at the documentation of boost canonical function, it sounds like it does the same thing as realpath, i.e. get an absolute path without symlinks and '.', '..' and double '/'. That sounds pretty close to what I want. – pythonic metaphor Feb 04 '14 at 19:27
  • There's a `readlink()` function in the POSIX C library. Apply it in a loop until it finds a file that is not a symlink. Done. –  Feb 04 '14 at 20:15

3 Answers3

3

Yes, realpath is the equivalent of readlink -f.

See the man page for more information

Robby75
  • 3,285
  • 6
  • 33
  • 52
1

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;
}
a1291762
  • 196
  • 1
  • 5
0

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.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487