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("");
}