in watchman/fs/FileDescriptor.cpp [360:477]
w_string FileDescriptor::readSymbolicLink() const {
#ifndef _WIN32
struct stat st;
if (fstat(fd_, &st)) {
throw std::system_error(
errno, std::generic_category(), "fstat for readSymbolicLink");
}
std::string result;
result.resize(st.st_size + 1, 0);
#ifdef __linux__
// Linux 2.6.39 and later provide this interface
auto atlen = readlinkat(fd_, "", &result[0], result.size());
if (atlen == int(result.size())) {
// It's longer than we expected; TOCTOU detected!
throw std::system_error(
ENAMETOOLONG,
std::generic_category(),
"readlinkat: link contents grew while examining file");
}
if (atlen >= 0) {
return w_string(result.data(), atlen);
}
// if we get ENOTDIR back then we're probably on an older linux and
// should fall back to the technique used below.
if (errno != ENOTDIR) {
throw std::system_error(
errno, std::generic_category(), "readlinkat for readSymbolicLink");
}
#endif
auto myName = getOpenedPath();
auto len = readlink(myName.c_str(), &result[0], result.size());
if (len == int(result.size())) {
// It's longer than we expected; TOCTOU detected!
throw std::system_error(
ENAMETOOLONG,
std::generic_category(),
"readlink: link contents grew while examining file");
}
if (len >= 0) {
return w_string(result.data(), len);
}
throw std::system_error(
errno, std::generic_category(), "readlink for readSymbolicLink");
#else // _WIN32
DWORD len = 64 * 1024;
auto buf = malloc(len);
if (!buf) {
throw std::bad_alloc();
}
SCOPE_EXIT {
free(buf);
};
WCHAR* target;
USHORT targetlen;
auto result = DeviceIoControl(
(HANDLE)fd_,
FSCTL_GET_REPARSE_POINT,
nullptr,
0,
buf,
len,
&len,
nullptr);
// We only give one retry; if the size changed again already, we'll
// have another pending notify from the OS to go look at it again
// later, and it's totally fine to give up here for now.
if (!result && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
free(buf);
buf = malloc(len);
if (!buf) {
throw std::bad_alloc();
}
result = DeviceIoControl(
(HANDLE)fd_,
FSCTL_GET_REPARSE_POINT,
nullptr,
0,
buf,
len,
&len,
nullptr);
}
if (!result) {
throw std::system_error(
GetLastError(), std::system_category(), "FSCTL_GET_REPARSE_POINT");
}
auto rep = reinterpret_cast<REPARSE_DATA_BUFFER*>(buf);
switch (rep->ReparseTag) {
case IO_REPARSE_TAG_SYMLINK:
target = rep->SymbolicLinkReparseBuffer.PathBuffer +
(rep->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
targetlen =
rep->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
break;
case IO_REPARSE_TAG_MOUNT_POINT:
target = rep->MountPointReparseBuffer.PathBuffer +
(rep->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
targetlen =
rep->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
break;
default:
throw std::system_error(
ENOSYS, std::generic_category(), "Unsupported ReparseTag");
}
return w_string(target, targetlen);
#endif
}