0

I want to store the path of the file the symbolic link is pointing to into bufff. This works with my current implementation using readlink but it grabs extra data I don't need because my bufsize is arbitrarily valued. I'm not sure how to size it according to the path of what the symbolic link points to because all I have is the path to the symbolic link. This is stored in path which is a char array. How do I know to size bufff with the size of the direct path string of the link if all I have is the path to the symbolic link?

char bufff[100];
size_t bufsize = 100;
readlink(path,bufff,bufsize);
John Smith
  • 283
  • 2
  • 19

3 Answers3

2

readlink() returns the length of the path, it doesn't put the trailing NUL into the buffer. You need to do it yourself:

size_t pathlength = readlink(path, bufff, sizeof(bufff)-1);
bufff[pathlength] = 0;

Note that I subtracted 1 from the size of the buffer, to ensure that there's room for the NUL.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • I often instead clear before the `path` buffer for `readlink` with a `memset`. So even on `readlink` failure the `path` has a sensible, NUL-terminated, string. On failure your `pathlength` is -1 and the `buff[pathlength]` assignment is *undefined behavior* (so the [solar system might collapse](http://stackoverflow.com/a/25636788/841108) if unlucky) – Basile Starynkevitch Sep 12 '14 at 21:48
2

The readlink() function returns the number of bytes copied to your buffer, without the final \0. This means that if you call readlink() with a buffer of 100 bytes and readlink() returns 100, you need more space (even if the path was exactly 100 bytes, you would still need at least 1 byte to add a null character at the end).

The solution is to increase your buffer in a loop:

size_t bufsize = 255; /* Initial buffer size */
ssize_t result;
char* buf = malloc(bufsize); /* Initial buffer allocation */
while ((result = readlink(path, buf, bufsize)) >= bufsize) {
    /* We double the buffer size, so next call should succeed ! */
    bufsize *= 2;
    buf = realloc(buf, bufsize);
}
buf[result] = '\0';

WARNING: This is just an example, we don't check if readlink returns -1 in case of errors. Same for malloc and realloc. You should check errors in real-world.

Thibaut D.
  • 2,521
  • 5
  • 22
  • 33
0

A complete function based on the answer of Thibaut D.:

char *do_get_symlink(char *path, struct stat attr) {

  /*
   * a much more elegant solution would be to use realpath(),
   * but it is 35% slower and produces different results on /proc
   */

  if (S_ISLNK(attr.st_mode)) {
    /*
     * st_size appears to be an unreliable source of the link length
     * PATH_MAX is artificial and not used by the GNU C Library
     */
    ssize_t length;
    size_t buffer = 128;
    char *symlink = malloc(sizeof(char) * buffer);

    if (!symlink) {
      fprintf(stderr, "%s: malloc(): %s\n", program, strerror(errno));
      return strdup("");
    }

    /*
     * if readlink() fills the buffer, double it and run again
     * even if it equals, because we need a character for the termination
     */
    while ((length = readlink(path, symlink, buffer)) > 0 && (size_t)length >= buffer) {
      buffer *= 2;
      symlink = realloc(symlink, buffer);

      if (!symlink) {
        fprintf(stderr, "%s: realloc(): %s\n", program, strerror(errno));
        return strdup("");
      }
    }

    if (length < 0) {
      fprintf(stderr, "%s: readlink(%s): %s\n", program, path, strerror(errno));
      free(symlink);
      return strdup("");
    }

    symlink[length] = '\0';
    return symlink;
  }

  /*
   * the entry is not a symlink
   * strdup is needed to keep the output free-able
   */
  return strdup("");
}
amq
  • 505
  • 1
  • 6
  • 16