-
Notifications
You must be signed in to change notification settings - Fork 666
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix readlink/readlinkat to return too long only when it is long #1109
Conversation
I'm afraid I don't understand the problem. Could you please be more clear? Give a complete example demonstrating the wrong behavior, and what you think the correct behavior should be. |
Oops sorry! Let me try to clarify. Consider the following folder structure
With nix when we try to let src = "/tmp/target";
let dest = "tmp/target/test-dir"; It works correctly when we pass in a buffer that is bigger than the src length let mut buf = vec![0; src.len() + 1];
println!("{}", str::from_utf8(&buf).unwrap()); // prints "/tmp/target" Actual: But when we pass in a buffer that is smaller than the let mut buf = vec![0; src.len()]; // returns `ENAMETOOLONG` Expected: Instead it should truncate the contents (to a length of buffer size), in case the buffer is too small to hold all of the contents. - from here |
Truncating the result is what readlink(2) does. But Nix is supposed to be safe and easy to use; we don't want to truncate any results. Perhaps |
That makes sense. I agree but this means we should always send in a buffer that is greater than the required size or if not it fails. We should at least allow the buffer to have the same size as that of the required result. What do you think?
Can we return EINVAL instead of ENAMETOOLONG? |
No, that would have the same problem. The problem with both of those error codes is that they overload errors used by the OS. Better to use our own error type. Better yet would be to never return an error. How about changing that function to allocate its storage internally and return an fn readlink<P: ...>(path: P) -> Result<OsString> {
let v = Vec::with_capacity(libc::PATH_MAX);
let r = libc::readlink(..., v.as_mut_ptr() as *mut c_char, v.capacity());
if let Ok(len) = Errno::result(r) {
unsafe { v.set_len(len) };
OsString::from_vec(v)
} else {
/* error handling */
}
} |
@asomers That is a good idea. I updated the code to reflect your suggested changes. |
CHANGELOG.md
Outdated
@@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). | |||
## [Unreleased] - ReleaseDate | |||
### Added | |||
### Changed | |||
- Changed `readlink` and `readlinkat` to return `osString` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first letter of OsString
should be capitalized. Also, since this change is backwards incompatible it would be courteous to our users to explain how to convert their existing code.
src/fcntl.rs
Outdated
} | ||
} | ||
} | ||
|
||
pub fn readlink<'a, P: ?Sized + NixPath>(path: &P, buffer: &'a mut [u8]) -> Result<&'a OsStr> { | ||
pub fn readlink<'a, P: ?Sized + NixPath>(path: &P) -> Result<OsString> { | ||
let mut v = vec![0u8; libc::PATH_MAX as usize]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to zero initialize. You can use an uninitialized vec instead.
src/fcntl.rs
Outdated
})?; | ||
|
||
wrap_readlink_result(buffer, res) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing space here.
([#1109](https://github.com/nix-rust/nix/pull/1109)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example isn't going to mean much to a reader. I'm thinking something in English, like "the buffer
argument of readlink
and readlinkat
has been removed, and the return value is now an owned type. Existing code can be updated by removing the buffer argument and removing any clone
or similar operation on the output."
src/fcntl.rs
Outdated
let res = path.with_nix_path(|cstr| { | ||
unsafe { libc::readlink(cstr.as_ptr(), v.as_mut_ptr() as *mut c_char, v.len() as size_t) } | ||
unsafe { libc::readlink(cstr.as_ptr(), v.as_mut_ptr() as *mut c_char, len as size_t) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You seem to be unaware of the capacity
method. You should write it like this:
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) }
CHANGELOG.md
Outdated
@@ -12,8 +12,16 @@ This project adheres to [Semantic Versioning](http://semver.org/). | |||
```rust | |||
use nix::fcntl::{readlink, readlinkat}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line isn't important to readers. You can suppress it with a leading #
, and delete the following blank line.
CHANGELOG.md
Outdated
// and removing any clone or similar operation on the output | ||
|
||
// old code `readlink(&path, &mut buf)` can be replaced with the following | ||
readlink(&path); // this returns OsString |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code would be more self-documenting like this:
let _: OsString = readlink(&path);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks for your submission!
bors r+
1109: fix readlink/readlinkat to return too long only when it is long r=asomers a=sendilkumarn Currently readlink returns `ENAMETOOLONG` when the bufferSize is equal to the result size. Consider the below case ```c++ int main(int argc, const char * argv[]) { std::string filename = "/tmp/test-dir/target"; size_t bufferSize = 9; char* buffer = new char[bufferSize]; size_t rc = readlink (filename.c_str(), buffer, bufferSize); // Since `readlink's` output depends on the `bufferSize`. If the`bufferSize` is 9 or greater than // 9 then it will return 9 or else it will return `bufferSize` as the value of `rc`. return 0; } ``` Co-authored-by: Sendil Kumar <sendilkumarn@live.com> Co-authored-by: Sendil Kumar N <sendilkumarn@live.com>
Build succeeded
|
Currently readlink returns
ENAMETOOLONG
when the bufferSize is equal to the result size.Consider the below case