about summary refs log tree commit diff
path: root/src/libutil/util.cc
diff options
context:
space:
mode:
authorTyson Whitehead <twhitehead@gmail.com>2017-10-27T16·15-0400
committerTyson Whitehead <twhitehead@gmail.com>2017-10-30T15·49-0400
commit07d2c6d213660e178fcdf128ea6f1f36d9d8a85d (patch)
treed453d16e0f67945d2ca909be7d2690d6af8e6e61 /src/libutil/util.cc
parent12991152be43f63634e48a7d118040e485125df4 (diff)
Fix (highly unlikely) race condition in readLink
Used to determine symlink size with stat and value with readlink.
This could technically result in garbage if symlink changed between
calls.  Also gets around the broken stat implementation in our
network filesystem (returns size + 1 giving a byte of garbage).
Diffstat (limited to 'src/libutil/util.cc')
-rw-r--r--src/libutil/util.cc23
1 files changed, 11 insertions, 12 deletions
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 9346d5dc4cf8..16c2c97aac9b 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -216,18 +216,17 @@ bool pathExists(const Path & path)
 Path readLink(const Path & path)
 {
     checkInterrupt();
-    struct stat st = lstat(path);
-    if (!S_ISLNK(st.st_mode))
-        throw Error(format("'%1%' is not a symlink") % path);
-    auto bufSize = std::max(st.st_size, (off_t) PATH_MAX + 1);
-    char buf[bufSize];
-    ssize_t rlsize = readlink(path.c_str(), buf, bufSize);
-    if (rlsize == -1)
-        throw SysError(format("reading symbolic link '%1%'") % path);
-    else if (rlsize > bufSize)
-        throw Error(format("symbolic link '%1%' size overflow %2% > %3%")
-            % path % rlsize % bufSize);
-    return string(buf, rlsize);
+    for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) {
+        char buf[bufSize];
+        ssize_t rlSize = readlink(path.c_str(), buf, bufSize);
+        if (rlSize == -1)
+            if (errno == EINVAL)
+                throw Error(format("'%1%' is not a symlink") % path);
+            else
+                throw SysError(format("reading symbolic link '%1%'") % path);
+        else if (rlSize < bufSize)
+            return string(buf, rlSize);
+    }
 }