Fix Syscall.readlink() for non-ascii targets
authorSteffen Kieß <s-kiess@web.de>
Sun, 28 Jul 2013 09:11:38 +0000 (11:11 +0200)
committerSteffen Kieß <s-kiess@web.de>
Sun, 28 Jul 2013 09:11:38 +0000 (11:11 +0200)
Syscall.readlink() currently returns an integer indicating the number of
bytes in the link. As buf contains chars, this value is useless if the
target contains non-ascii characters.

This commit creates a new overload which uses a byte array instead of a
StringBuilder and rewrites the old overload to return the number of chars
instead.

Fixes #11778 and #9611

mcs/class/Mono.Management/Mono.Attach/VirtualMachine.cs
mcs/class/Mono.Posix/Mono.Posix_test.dll.sources
mcs/class/Mono.Posix/Mono.Unix.Native/Syscall.cs
mcs/class/Mono.Posix/Mono.Unix/UnixPath.cs
mcs/class/Mono.Posix/Mono.Unix/UnixSymbolicLinkInfo.cs
mcs/class/Mono.Posix/Test/Mono.Unix/ReadlinkTest.cs [new file with mode: 0644]
support/map.h
support/unistd.c

index d6205a2d4d14f4f1a9f53a695f27a26cdfd5a3a5..7e57265a62a5bc3202dc3f36df7ddb1fcd40f25d 100644 (file)
@@ -42,20 +42,7 @@ namespace Mono.Attach
                }
 
                public string GetWorkingDirectory () {
-                       int len = 256;
-
-                       while (true) {
-                               StringBuilder sb = new StringBuilder (len);
-
-                               int res = Syscall.readlink ("/proc/" + pid + "/cwd", sb);
-                               if (res == -1)
-                                       throw new IOException ("Syscall.readlink () failed with error " + res + ".");
-                               else if (res == len) {
-                                       len = len * 2;
-                               } else {
-                                       return sb.ToString ();
-                               }
-                       }
+                       return UnixPath.ReadLink ("/proc/" + pid + "/cwd");
                }
 
                /*
index b0c430596b21d5d9457befdeb3815cea9e9e1a2d..270b0b6aee680cb64332b06638110b58a6eda18b 100644 (file)
@@ -1,3 +1,4 @@
+Mono.Unix/ReadlinkTest.cs
 Mono.Unix/StdioFileStreamTest.cs
 Mono.Unix/UnixEncodingTest.cs
 Mono.Unix/UnixGroupTest.cs
index 27d241539d5a7e3a35ea4d74436ff211bdfd4afb..7488f79b56f2217deecc2832d1f13822ee9bfc40 100644 (file)
@@ -3937,17 +3937,51 @@ namespace Mono.Unix.Native {
                                [MarshalAs (UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(FileNameMarshaler))]
                                string newpath);
 
+               delegate long DoReadlinkFun (byte[] target);
+
+               // Helper function for readlink(string, StringBuilder) and readlinkat (int, string, StringBuilder)
+               static int ReadlinkIntoStringBuilder (DoReadlinkFun doReadlink, [Out] StringBuilder buf, ulong bufsiz)
+               {
+                       // bufsiz > int.MaxValue can't work because StringBuilder can store only int.MaxValue chars
+                       int bufsizInt = checked ((int) bufsiz);
+                       var target = new byte [bufsizInt];
+
+                       var r = doReadlink (target);
+                       if (r < 0)
+                               return checked ((int) r);
+
+                       buf.Length = 0;
+                       var chars = UnixEncoding.Instance.GetChars (target, 0, checked ((int) r));
+                       // Make sure that at more bufsiz chars are written
+                       buf.Append (chars, 0, System.Math.Min (bufsizInt, chars.Length));
+                       if (r == bufsizInt) {
+                               // may not have read full contents; fill 'buf' so that caller can properly check
+                               buf.Append (new string ('\x00', bufsizInt - buf.Length));
+                       }
+                       return buf.Length;
+               }
+
                // readlink(2)
-               //    int readlink(const char *path, char *buf, size_t bufsize);
+               //    ssize_t readlink(const char *path, char *buf, size_t bufsize);
+               public static int readlink (string path, [Out] StringBuilder buf, ulong bufsiz)
+               {
+                       return ReadlinkIntoStringBuilder (target => readlink (path, target), buf, bufsiz);
+               }
+
+               public static int readlink (string path, [Out] StringBuilder buf)
+               {
+                       return readlink (path, buf, (ulong) buf.Capacity);
+               }
+
                [DllImport (MPH, SetLastError=true,
                                EntryPoint="Mono_Posix_Syscall_readlink")]
-               public static extern int readlink (
+               private static extern long readlink (
                                [MarshalAs (UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(FileNameMarshaler))]
-                               string path, [Out] StringBuilder buf, ulong bufsiz);
+                               string path, byte[] buf, ulong bufsiz);
 
-               public static int readlink (string path, [Out] StringBuilder buf)
+               public static long readlink (string path, byte[] buf)
                {
-                       return readlink (path, buf, (ulong) buf.Capacity);
+                       return readlink (path, buf, (ulong) buf.LongLength);
                }
 
                [DllImport (LIBC, SetLastError=true)]
@@ -4226,16 +4260,26 @@ namespace Mono.Unix.Native {
                }
 
                // readlinkat(2)
-               //    int readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsize);
+               //    ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsize);
+               public static int readlinkat (int dirfd, string pathname, [Out] StringBuilder buf, ulong bufsiz)
+               {
+                       return ReadlinkIntoStringBuilder (target => readlinkat (dirfd, pathname, target), buf, bufsiz);
+               }
+
+               public static int readlinkat (int dirfd, string pathname, [Out] StringBuilder buf)
+               {
+                       return readlinkat (dirfd, pathname, buf, (ulong) buf.Capacity);
+               }
+
                [DllImport (MPH, SetLastError=true,
                                EntryPoint="Mono_Posix_Syscall_readlinkat")]
-               public static extern int readlinkat (int dirfd,
+               private static extern long readlinkat (int dirfd,
                                [MarshalAs (UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(FileNameMarshaler))]
-                               string pathname, [Out] StringBuilder buf, ulong bufsiz);
+                               string pathname, byte[] buf, ulong bufsiz);
 
-               public static int readlinkat (int dirfd, string pathname, [Out] StringBuilder buf)
+               public static long readlinkat (int dirfd, string pathname, byte[] buf)
                {
-                       return readlinkat (dirfd, pathname, buf, (ulong) buf.Capacity);
+                       return readlinkat (dirfd, pathname, buf, (ulong) buf.LongLength);
                }
 
                [DllImport (LIBC, SetLastError=true)]
index 659e0bd6d50ad7059b57aedad352a1741a210c65..e577edb74d4508f668dcc99a7bf282508160f843 100644 (file)
@@ -209,69 +209,59 @@ namespace Mono.Unix {
 
                // Read the specified symbolic link.  If the file isn't a symbolic link,
                // return null; otherwise, return the contents of the symbolic link.
-               //
-               // readlink(2) is horribly evil, as there is no way to query how big the
-               // symlink contents are.  Consequently, it's trial and error...
                internal static string ReadSymbolicLink (string path)
                {
-                       StringBuilder buf = new StringBuilder (256);
+                       string target = TryReadLink (path);
+                       if (target == null) {
+                               Native.Errno errno = Native.Stdlib.GetLastError ();
+                               if (errno != Native.Errno.EINVAL)
+                                       UnixMarshal.ThrowExceptionForError (errno);
+                       }
+                       return target;
+               }
+
+               public static string TryReadLink (string path)
+               {
+                       byte[] buf = new byte[256];
                        do {
-                               int r = Native.Syscall.readlink (path, buf);
-                               if (r < 0) {
-                                       Native.Errno e;
-                                       switch (e = Native.Stdlib.GetLastError()) {
-                                       case Native.Errno.EINVAL:
-                                               // path isn't a symbolic link
-                                               return null;
-                                       default:
-                                               UnixMarshal.ThrowExceptionForError (e);
-                                               break;
-                                       }
-                               }
-                               else if (r == buf.Capacity) {
-                                       buf.Capacity *= 2;
-                               }
+                               long r = Native.Syscall.readlink (path, buf);
+                               if (r < 0)
+                                       return null;
+                               else if (r == buf.Length)
+                                       buf = new byte[checked (buf.LongLength * 2)];
                                else
-                                       return buf.ToString (0, r);
+                                       return UnixEncoding.Instance.GetString (buf, 0, checked ((int) r));
                        } while (true);
                }
 
-               // Read the specified symbolic link.  If the file isn't a symbolic link,
-               // return null; otherwise, return the contents of the symbolic link.
-               //
-               // readlink(2) is horribly evil, as there is no way to query how big the
-               // symlink contents are.  Consequently, it's trial and error...
-               private static string ReadSymbolicLink (string path, out Native.Errno errno)
+               public static string TryReadLinkAt (int dirfd, string path)
                {
-                       errno = (Native.Errno) 0;
-                       StringBuilder buf = new StringBuilder (256);
+                       byte[] buf = new byte[256];
                        do {
-                               int r = Native.Syscall.readlink (path, buf);
-                               if (r < 0) {
-                                       errno = Native.Stdlib.GetLastError ();
+                               long r = Native.Syscall.readlinkat (dirfd, path, buf);
+                               if (r < 0)
                                        return null;
-                               }
-                               else if (r == buf.Capacity) {
-                                       buf.Capacity *= 2;
-                               }
+                               else if (r == buf.Length)
+                                       buf = new byte[checked (buf.LongLength * 2)];
                                else
-                                       return buf.ToString (0, r);
+                                       return UnixEncoding.Instance.GetString (buf, 0, checked ((int) r));
                        } while (true);
                }
 
-               public static string TryReadLink (string path)
+               public static string ReadLink (string path)
                {
-                       Native.Errno errno;
-                       return ReadSymbolicLink (path, out errno);
+                       string target = TryReadLink (path);
+                       if (target == null)
+                               UnixMarshal.ThrowExceptionForLastError (); 
+                       return target;
                }
 
-               public static string ReadLink (string path)
+               public static string ReadLinkAt (int dirfd, string path)
                {
-                       Native.Errno errno;
-                       path = ReadSymbolicLink (path, out errno);
-                       if (errno != 0)
-                               UnixMarshal.ThrowExceptionForError (errno);
-                       return path;
+                       string target = TryReadLinkAt (dirfd, path);
+                       if (target == null)
+                               UnixMarshal.ThrowExceptionForLastError (); 
+                       return target;
                }
 
                public static bool IsPathRooted (string path)
index d0026d2b46ab8333b4cd8562f5057f825879f9db..242a294a2a1a1cb970126dcc436437fb61e35b9a 100644 (file)
@@ -56,19 +56,18 @@ namespace Mono.Unix {
 
                public string ContentsPath {
                        get {
-                               return ReadLink ();
+                               return UnixPath.ReadLink (FullPath);
                        }
                }
 
                public bool HasContents {
                        get {
-                               return TryReadLink () != null;
+                               return UnixPath.TryReadLink (FullPath) != null;
                        }
                }
 
                public UnixFileSystemInfo GetContents ()
                {
-                       ReadLink ();
                        return UnixFileSystemInfo.GetFileSystemEntry (
                                                UnixPath.Combine (UnixPath.GetDirectoryName (FullPath), 
                                                        ContentsPath));
@@ -103,23 +102,6 @@ namespace Mono.Unix {
                {
                        return Native.Syscall.lstat (path, out stat) == 0;
                }
-
-               private string ReadLink ()
-               {
-                       string r = TryReadLink ();
-                       if (r == null)
-                               UnixMarshal.ThrowExceptionForLastError ();
-                       return r;
-               }
-
-               private string TryReadLink ()
-               {
-                       StringBuilder sb = new StringBuilder ((int) base.Length+1);
-                       int r = Native.Syscall.readlink (FullPath, sb);
-                       if (r == -1)
-                               return null;
-                       return sb.ToString (0, r);
-               }
        }
 }
 
diff --git a/mcs/class/Mono.Posix/Test/Mono.Unix/ReadlinkTest.cs b/mcs/class/Mono.Posix/Test/Mono.Unix/ReadlinkTest.cs
new file mode 100644 (file)
index 0000000..d50b2d5
--- /dev/null
@@ -0,0 +1,285 @@
+//
+// readlink() / readlinkat() Test Cases
+//
+// Authors:
+//  Steffen Kiess (s-kiess@web.de)
+//
+// Copyright (C) 2013 Steffen Kiess
+//
+
+using System;
+using System.IO;
+using System.Text;
+
+using Mono.Unix;
+using Mono.Unix.Native;
+
+using NUnit.Framework;
+
+namespace MonoTests.Mono.Unix
+{
+       [TestFixture, Category ("NotDotNet")]
+       public class ReadlinkTest {
+
+               static string[] Targets = {
+                       // Simple test cases
+                       "a",
+                       "test",
+                       // With non-ASCII characters
+                       "ä",
+                       "test ö test",
+                       // With non-UTF8 bytes
+                       UnixEncoding.Instance.GetString (new byte[] {0xff, 0x80, 0x41, 0x80}),
+                       // Size is roughly initial size of buffer
+                       new string ('a', 255),
+                       new string ('a', 256),
+                       new string ('a', 257),
+                       // With non-ASCII characters, size is roughly initial size of buffer
+                       "ä" + new string ('a', 253), // 254 chars, 255 bytes
+                       "ä" + new string ('a', 254), // 255 chars, 256 bytes
+                       "ä" + new string ('a', 255), // 256 chars, 257 bytes
+                       "ä" + new string ('a', 256), // 257 chars, 258 bytes
+                       new string ('a', 253) + "ä", // 254 chars, 255 bytes
+                       new string ('a', 254) + "ä", // 255 chars, 256 bytes
+                       new string ('a', 255) + "ä", // 256 chars, 257 bytes
+                       new string ('a', 256) + "ä", // 257 chars, 258 bytes
+                       // With non-UTF8 bytes, size is roughly initial size of buffer
+                       "\0\u00ff" + new string ('a', 253), // 255 chars, 254 bytes
+                       "\0\u00ff" + new string ('a', 254), // 256 chars, 255 bytes
+                       "\0\u00ff" + new string ('a', 255), // 257 chars, 256 bytes
+                       "\0\u00ff" + new string ('a', 256), // 258 chars, 257 bytes
+                       new string ('a', 253) + "\0\u00ff", // 255 chars, 254 bytes
+                       new string ('a', 254) + "\0\u00ff", // 256 chars, 255 bytes
+                       new string ('a', 255) + "\0\u00ff", // 257 chars, 256 bytes
+                       new string ('a', 256) + "\0\u00ff", // 258 chars, 257 bytes
+               };
+
+               bool HaveReadlinkAt;
+               string TempFolder;
+               int TempFD;
+
+               [SetUp]
+               public void SetUp ()
+               {
+                       HaveReadlinkAt = false;
+                       try {
+                               Syscall.readlinkat (-1, "", new byte[1]);
+                               HaveReadlinkAt = true;
+                       } catch (EntryPointNotFoundException) {
+                       }
+
+
+                       TempFolder = Path.Combine (Path.GetTempPath (), this.GetType ().FullName);
+
+                       if (Directory.Exists (TempFolder))
+                               //Directory.Delete (TempFolder, true); // Fails for long link target paths
+                               new UnixDirectoryInfo (TempFolder).Delete (true);
+
+                       Directory.CreateDirectory (TempFolder);
+
+                       TempFD = Syscall.open (TempFolder, OpenFlags.O_RDONLY | OpenFlags.O_DIRECTORY);
+                       if (TempFD < 0)
+                               UnixMarshal.ThrowExceptionForLastError ();
+               }
+
+               [TearDown]
+               public void TearDown()
+               {
+                       if (Syscall.close (TempFD) < 0)
+                               UnixMarshal.ThrowExceptionForLastError ();
+
+                       if (Directory.Exists (TempFolder))
+                               //Directory.Delete (TempFolder, true); // Fails for long link target paths
+                               new UnixDirectoryInfo (TempFolder).Delete (true);
+               }
+
+               void CreateLink (string s)
+               {
+                               string link = UnixPath.Combine (TempFolder, "link");
+
+                               //File.Delete (link); // Fails for long link target paths
+                               if (Syscall.unlink (link) < 0 && Stdlib.GetLastError () != Errno.ENOENT)
+                                       UnixMarshal.ThrowExceptionForLastError ();
+
+                               if (Syscall.symlink (s, link) < 0)
+                                       UnixMarshal.ThrowExceptionForLastError ();
+               }
+
+               [Test]
+               public void ReadLink ()
+               {
+                       foreach (string s in Targets) {
+                               string link = UnixPath.Combine (TempFolder, "link");
+
+                               CreateLink (s);
+
+                               var target = UnixPath.ReadLink (link);
+                               Assert.AreEqual (s, target);
+                       }
+               }
+
+               [Test]
+               public void ReadLinkAt ()
+               {
+                       if (!HaveReadlinkAt)
+                               return;
+
+                       foreach (string s in Targets) {
+                               CreateLink (s);
+
+                               var target = UnixPath.ReadLinkAt (TempFD, "link");
+                               Assert.AreEqual (s, target);
+                       }
+               }
+
+               [Test]
+               public void TryReadLink ()
+               {
+                       foreach (string s in Targets) {
+                               string link = UnixPath.Combine (TempFolder, "link");
+
+                               CreateLink (s);
+
+                               var target = UnixPath.TryReadLink (link);
+                               Assert.AreEqual (s, target);
+                       }
+               }
+
+               [Test]
+               public void TryReadLinkAt ()
+               {
+                       if (!HaveReadlinkAt)
+                               return;
+
+                       foreach (string s in Targets) {
+                               CreateLink (s);
+
+                               var target = UnixPath.TryReadLinkAt (TempFD, "link");
+                               Assert.AreEqual (s, target);
+                       }
+               }
+
+               [Test]
+               public void readlink_byte ()
+               {
+                       foreach (string s in Targets) {
+                               string link = UnixPath.Combine (TempFolder, "link");
+
+                               CreateLink (s);
+
+                               string target = null;
+                               byte[] buf = new byte[256];
+                               do {
+                                       long r = Syscall.readlink (link, buf);
+                                       if (r < 0)
+                                               UnixMarshal.ThrowExceptionForLastError ();
+                                       Assert.GreaterOrEqual (buf.Length, r);
+                                       if (r == buf.Length)
+                                               buf = new byte[checked (buf.Length * 2)];
+                                       else
+                                               target = UnixEncoding.Instance.GetString (buf, 0, checked ((int) r));
+                               } while (target == null);
+
+                               Assert.AreEqual (s, target);
+                       }
+               }
+
+               [Test]
+               public void readlinkat_byte ()
+               {
+                       if (!HaveReadlinkAt)
+                               return;
+
+                       foreach (string s in Targets) {
+                               CreateLink (s);
+
+                               string target = null;
+                               byte[] buf = new byte[256];
+                               do {
+                                       long r = Syscall.readlinkat (TempFD, "link", buf);
+                                       if (r < 0)
+                                               UnixMarshal.ThrowExceptionForLastError ();
+                                       Assert.GreaterOrEqual (buf.Length, r);
+                                       if (r == buf.Length)
+                                               buf = new byte[checked (buf.Length * 2)];
+                                       else
+                                               target = UnixEncoding.Instance.GetString (buf, 0, checked ((int) r));
+                               } while (target == null);
+
+                               Assert.AreEqual (s, target);
+                       }
+               }
+
+               [Test]
+               public void readlink_char ()
+               {
+                       foreach (string s in Targets) {
+                               string link = UnixPath.Combine (TempFolder, "link");
+
+                               CreateLink (s);
+
+                               var sb = new StringBuilder (256);
+                               do {
+                                       int oldCapacity = sb.Capacity;
+                                       int r = Syscall.readlink (link, sb);
+                                       Assert.AreEqual (oldCapacity, sb.Capacity);
+                                       if (r < 0)
+                                               UnixMarshal.ThrowExceptionForLastError ();
+                                       Assert.AreEqual (r, sb.Length);
+                                       Assert.GreaterOrEqual (sb.Capacity, r);
+                                       if (r == sb.Capacity)
+                                               checked { sb.Capacity *= 2; }
+                                       else
+                                               break;
+                               } while (true);
+                               var target = sb.ToString ();
+
+                               Assert.AreEqual (s, target);
+                       }
+               }
+
+               [Test]
+               public void readlinkat_char ()
+               {
+                       foreach (string s in Targets) {
+                               CreateLink (s);
+
+                               var sb = new StringBuilder (256);
+                               do {
+                                       int oldCapacity = sb.Capacity;
+                                       int r = Syscall.readlinkat (TempFD, "link", sb);
+                                       Assert.AreEqual (oldCapacity, sb.Capacity);
+                                       if (r < 0)
+                                               UnixMarshal.ThrowExceptionForLastError ();
+                                       Assert.AreEqual (r, sb.Length);
+                                       Assert.GreaterOrEqual (sb.Capacity, r);
+                                       if (r == sb.Capacity)
+                                               checked { sb.Capacity *= 2; }
+                                       else
+                                               break;
+                               } while (true);
+                               var target = sb.ToString ();
+
+                               Assert.AreEqual (s, target);
+                       }
+               }
+
+               [Test]
+               public void ReadlinkMultiByteChar ()
+               {
+                       string link = UnixPath.Combine (TempFolder, "link");
+
+                       CreateLink ("á");
+
+                       var sb = new StringBuilder (2);
+                       int res = Syscall.readlink (link, sb);
+                       if (res < 0)
+                               UnixMarshal.ThrowExceptionForLastError ();
+
+                       Assert.AreEqual (res, 2);
+                       Assert.AreEqual (sb.Length, 2);
+                       Assert.AreEqual (sb.Capacity, 2);
+                       Assert.AreEqual (sb.ToString (), "á\u0000");
+               }
+       }
+}
index a78caf0ef9e478cc30e1f40e3a8afa6b90f73aa2..233c2b113148075a1a03e3ebf5dc99a4d3723801 100644 (file)
@@ -1780,8 +1780,8 @@ gint64 Mono_Posix_Syscall_pwritev (int fd, struct Mono_Posix_Iovec* iov, int iov
 gint64 Mono_Posix_Syscall_read (int fd, void* buf, guint64 count);
 int Mono_Posix_Syscall_readdir (void* dir, struct Mono_Posix_Syscall__Dirent* dentry);
 int Mono_Posix_Syscall_readdir_r (void* dirp, struct Mono_Posix_Syscall__Dirent* entry, void** result);
-int Mono_Posix_Syscall_readlink (const char* path, char* buf, guint64 bufsiz);
-int Mono_Posix_Syscall_readlinkat (int dirfd, const char* pathname, char* buf, guint64 bufsiz);
+gint64 Mono_Posix_Syscall_readlink (const char* path, unsigned char* buf, guint64 bufsiz);
+gint64 Mono_Posix_Syscall_readlinkat (int dirfd, const char* pathname, unsigned char* buf, guint64 bufsiz);
 gint64 Mono_Posix_Syscall_readv (int fd, struct Mono_Posix_Iovec* iov, int iovcnt);
 int Mono_Posix_Syscall_remap_file_pages (void* start, guint64 size, int prot, gint64 pgoff, int flags);
 int Mono_Posix_Syscall_removexattr (const char* path, const char* name);
index 7fe7dac836324121782e1bb80d7878a7fce1b55b..1123f2bc0fac9112b60e8f398e11b685eb59783c 100644 (file)
@@ -138,24 +138,24 @@ Mono_Posix_Syscall_ttyname_r (int fd, char *buf, mph_size_t len)
 }
 #endif /* ndef HAVE_TTYNAME_R */
 
-gint32
-Mono_Posix_Syscall_readlink (const char *path, char *buf, mph_size_t len)
+gint64
+Mono_Posix_Syscall_readlink (const char *path, unsigned char *buf, mph_size_t len)
 {
-       int r;
+       gint64 r;
        mph_return_if_size_t_overflow (len);
-       r = readlink (path, buf, (size_t) len);
+       r = readlink (path, (char*) buf, (size_t) len);
        if (r >= 0 && r < len)
                buf [r] = '\0';
        return r;
 }
 
 #ifdef HAVE_READLINKAT
-gint32
-Mono_Posix_Syscall_readlinkat (int dirfd, const char *path, char *buf, mph_size_t len)
+gint64
+Mono_Posix_Syscall_readlinkat (int dirfd, const char *path, unsigned char *buf, mph_size_t len)
 {
-       int r;
+       gint64 r;
        mph_return_if_size_t_overflow (len);
-       r = readlinkat (dirfd, path, buf, (size_t) len);
+       r = readlinkat (dirfd, path, (char*) buf, (size_t) len);
        if (r >= 0 && r < len)
                buf [r] = '\0';
        return r;