diff options
author | Solra Bizna <solra@bizna.name> | 2020-05-03 13:18:52 -0600 |
---|---|---|
committer | Solra Bizna <solra@bizna.name> | 2020-05-10 22:48:56 -0600 |
commit | dfee424c025bfd921a9a3e56ac786ed778bb91e1 (patch) | |
tree | 5d9e0c60da4ab076a4752b0b50c2cd6b1422fec4 /src/fcntl.rs | |
parent | a937988d7c31d36fe47c136c2c4d8942ea236bd8 (diff) | |
download | nix-dfee424c025bfd921a9a3e56ac786ed778bb91e1.zip |
Add support for reading symlinks longer than `PATH_MAX` to `readlink` and `readlinkat`
Diffstat (limited to 'src/fcntl.rs')
-rw-r--r-- | src/fcntl.rs | 85 |
1 files changed, 66 insertions, 19 deletions
diff --git a/src/fcntl.rs b/src/fcntl.rs index 1dddee73..5bdc9dda 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -180,33 +180,80 @@ pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(old_dirfd: Option<Ra Errno::result(res).map(drop) } -fn wrap_readlink_result(v: &mut Vec<u8>, res: ssize_t) -> Result<OsString> { - match Errno::result(res) { - Err(err) => Err(err), - Ok(len) => { - unsafe { v.set_len(len as usize) } - Ok(OsString::from_vec(v.to_vec())) +fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> { + unsafe { v.set_len(len as usize) } + v.shrink_to_fit(); + Ok(OsString::from_vec(v.to_vec())) +} + +fn readlink_maybe_at<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P, + v: &mut Vec<u8>) + -> Result<libc::ssize_t> { + path.with_nix_path(|cstr| { + unsafe { + match dirfd { + Some(dirfd) => libc::readlinkat(dirfd, cstr.as_ptr(), + v.as_mut_ptr() as *mut c_char, + v.capacity() as size_t), + None => libc::readlink(cstr.as_ptr(), + v.as_mut_ptr() as *mut c_char, + v.capacity() as size_t), + } } - } + }) } -pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> { +fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) + -> Result<OsString> { let mut v = Vec::with_capacity(libc::PATH_MAX as usize); - let res = path.with_nix_path(|cstr| { - unsafe { libc::readlink(cstr.as_ptr(), v.as_mut_ptr() as *mut c_char, v.capacity() as size_t) } - })?; - - wrap_readlink_result(&mut v, res) + // simple case: result is strictly less than `PATH_MAX` + let res = readlink_maybe_at(dirfd, path, &mut v)?; + let len = Errno::result(res)?; + debug_assert!(len >= 0); + if (len as usize) < v.capacity() { + return wrap_readlink_result(v, res); + } + // Uh oh, the result is too long... + // Let's try to ask lstat how many bytes to allocate. + let reported_size = super::sys::stat::lstat(path) + .and_then(|x| Ok(x.st_size)).unwrap_or(0); + let mut try_size = if reported_size > 0 { + // Note: even if `lstat`'s apparently valid answer turns out to be + // wrong, we will still read the full symlink no matter what. + reported_size as usize + 1 + } else { + // If lstat doesn't cooperate, or reports an error, be a little less + // precise. + (libc::PATH_MAX as usize).max(128) << 1 + }; + loop { + v.reserve_exact(try_size); + let res = readlink_maybe_at(dirfd, path, &mut v)?; + let len = Errno::result(res)?; + debug_assert!(len >= 0); + if (len as usize) < v.capacity() { + break wrap_readlink_result(v, res); + } + else { + // Ugh! Still not big enough! + match try_size.checked_shl(1) { + Some(next_size) => try_size = next_size, + // It's absurd that this would happen, but handle it sanely + // anyway. + None => break Err(super::Error::Sys(Errno::ENAMETOOLONG)) + } + } + } } +pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> { + inner_readlink(None, path) +} -pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P) -> Result<OsString> { - let mut v = Vec::with_capacity(libc::PATH_MAX as usize); - let res = path.with_nix_path(|cstr| { - unsafe { libc::readlinkat(dirfd, cstr.as_ptr(), v.as_mut_ptr() as *mut c_char, v.capacity() as size_t) } - })?; - wrap_readlink_result(&mut v, res) +pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P) + -> Result<OsString> { + inner_readlink(Some(dirfd), path) } /// Computes the raw fd consumed by a function of the form `*at`. |