Howto use readlink with dynamic memory allocation
Asked Answered
D

6

7

Problem:

On a linux machine I want to read the target string of a link. From documentation I have found the following code sample (without error processing):

struct stat sb;
ssize_t r;
char * linkname;

lstat("<some link>", &sb);
linkname = malloc(sb.st_size + 1);
r = readlink("/proc/self/exe", linkname, sb.st_size + 1);

The probelm is that sb.st_size returns 0 for links on my system.

So how does one allocate memory dynamically for readline on such systems?

Many thanks!


One possible solution:

For future reference. Using the points made by jilles:

struct stat sb;
ssize_t r = INT_MAX;
int linkSize = 0;
const int growthRate = 255;

char * linkTarget = NULL;

// get length of the pathname the link points to
if (lstat("/proc/self/exe", &sb) == -1) {   // could not lstat: insufficient permissions on directory?
    perror("lstat");
    return;
}

// read the link target into a string
linkSize = sb.st_size + 1 - growthRate;
while (r >= linkSize) { // i.e. symlink increased in size since lstat() or non-POSIX compliant filesystem
    // allocate sufficient memory to hold the link
    linkSize += growthRate;
    free(linkTarget);
    linkTarget = malloc(linkSize);
    if (linkTarget == NULL) {           // insufficient memory
        fprintf(stderr, "setProcessName(): insufficient memory\n");
        return;
    }

    // read the link target into variable linkTarget
    r = readlink("/proc/self/exe", linkTarget, linkSize);
    if (r < 0) {        // readlink failed: link was deleted?
        perror("lstat");
        return;
    }
}
linkTarget[r] = '\0';   // readlink does not null-terminate the string
Dialytic answered 21/2, 2012 at 21:24 Comment(0)
M
9

POSIX says the st_size field for a symlink shall be set to the length of the pathname in the link (without '\0'). However, the /proc filesystem on Linux is not POSIX-compliant. (It has more violations than just this one, such as when reading certain files one byte at a time.)

You can allocate a buffer of a certain size, try readlink() and retry with a larger buffer if the buffer was not large enough (readlink() returned as many bytes as fit in the buffer), until the buffer is large enough.

Alternatively you can use PATH_MAX and break portability to systems where it is not a compile-time constant or where the pathname may be longer than that (POSIX permits either).

Miracle answered 21/2, 2012 at 22:3 Comment(2)
Thanks for the point about the /proc filesystem. I was not aware of this. So st_size "should" work anywhere except in /proc. It looks like an iterative approach has to be used as a fall-back solution for universal code.Dialytic
@ArikRaffaelFunke: At best the st_size field is only indicative anyway, because the link could be changed between the lstat() call and the readlink() call. So you need to be prepared to handle the size being insufficient anyway.Arrowworm
C
4

The other answers don't mention it, but there is the realpath function, that does exactly what you want, which is specified by POSIX.1-2001.

char *realpath(const char *path, char *resolved_path);

from the manpage:

realpath() expands all symbolic links and resolves references to /./, /../ and extra '/' characters in the null-terminated string named by path to produce a canonicalized absolute pathname.

realpath also handles the dynamic memory allocation for you, if you want. Again, excerpt from the manpage:

If resolved_path is specified as NULL, then realpath() uses malloc(3) to allocate a buffer of up to PATH_MAX bytes to hold the resolved pathname, and returns a pointer to this buffer. The caller should deallocate this buffer using free(3).

As a simple, complete example:

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>    

int
resolve_link (const char *filename)
{
  char *res = realpath(filename, NULL);
  if (res == NULL)
    {
      perror("realpath failed");
      return -1;
    }

  printf("%s -> %s\n", filename, res);
  free(res);

  return 0;
}

int
main (void)
{
  resolve_link("/proc/self/exe");
  return 0;
}
Couvade answered 13/12, 2014 at 15:19 Comment(1)
I wish more library functions had the option to automatically allocate the string buffer. Why don't they?Eichmann
S
1

st_size does not give the correct answer on /proc.

Instead you can malloc PATH_MAX, or pathconf(_PC_PATH_MAX) bytes. That should be enough for most cases. If you want to be able to handle paths longer than that, you can call readlink in a loop and reallocate your buffer if the readlink return value indicates that the buffer is too short. Note though that many other POSIX functions simply assume PATH_MAX is enough.

Sanity answered 21/2, 2012 at 21:37 Comment(1)
Unfortunately PATH_MAX does not help. On my system it is 4096 but on an ext3 filesystem there is nothing preventing you from having paths longer than that.Dialytic
T
0

I'm a bit puzzled as to why st_size is zero. Per POSIX:

For symbolic links, the st_mode member shall contain meaningful information when used with the file type macros. The file mode bits in st_mode are unspecified. The structure members st_ino, st_dev, st_uid, st_gid, st_atim, st_ctim, and st_mtim shall have meaningful values and the value of the st_nlink member shall be set to the number of (hard) links to the symbolic link. The value of the st_size member shall be set to the length of the pathname contained in the symbolic link not including any terminating null byte.

Source: http://pubs.opengroup.org/onlinepubs/9699919799/functions/lstat.html

If st_size does not work, I think your only option is to dynamically allocate a buffer and keep resizing it larger as long as the return value of readlink is equal to the buffer size.

Togliatti answered 21/2, 2012 at 22:7 Comment(2)
Even if st_size does work, the link destination could be changed between the lstat() call and the readlink() call.Arrowworm
Indeed, in which case you'll have to detect truncation and retry.Togliatti
L
0

The manpage for readlink(2) says it will silently truncate if the buffer is too small. If you truly want to be unbounded (and don't mind paying some cost for extra work) you can start with a given allocation size and keep increasing it and re-trying the readlink call. You can stop growing the buffer when the next call to readlink returns the same string it did for the last iteration.

Lamori answered 21/2, 2012 at 22:9 Comment(0)
P
-1

What exactly are you trying to achieve with the lstat?

You should be able to get the target with just the following

char buffer[1024];
ssize_t r = readlink ("/proc/self/exe", buffer, 1024);
buffer[r] = 0;

printf ("%s\n", buffer);

If you're trying to get the length of the file name size, I don't think st_size is the right variable for that... But that's possibly a different question.

Pageantry answered 21/2, 2012 at 21:35 Comment(1)
This does not help as I am looking for a way to dynamically allocate the required size. You are implicitly limiting the length of paths to 1024 character. This will be enough in most cases - but there is no guarantee.Dialytic

© 2022 - 2024 — McMap. All rights reserved.