@@ -180,33 +180,80 @@ pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(old_dirfd: Option<Ra
180
180
Errno :: result ( res) . map ( drop)
181
181
}
182
182
183
- fn wrap_readlink_result ( v : & mut Vec < u8 > , res : ssize_t ) -> Result < OsString > {
184
- match Errno :: result ( res) {
185
- Err ( err) => Err ( err) ,
186
- Ok ( len) => {
187
- unsafe { v. set_len ( len as usize ) }
188
- Ok ( OsString :: from_vec ( v. to_vec ( ) ) )
183
+ fn wrap_readlink_result ( mut v : Vec < u8 > , len : ssize_t ) -> Result < OsString > {
184
+ unsafe { v. set_len ( len as usize ) }
185
+ v. shrink_to_fit ( ) ;
186
+ Ok ( OsString :: from_vec ( v. to_vec ( ) ) )
187
+ }
188
+
189
+ fn readlink_maybe_at < P : ?Sized + NixPath > ( dirfd : Option < RawFd > , path : & P ,
190
+ v : & mut Vec < u8 > )
191
+ -> Result < libc:: ssize_t > {
192
+ path. with_nix_path ( |cstr| {
193
+ unsafe {
194
+ match dirfd {
195
+ Some ( dirfd) => libc:: readlinkat ( dirfd, cstr. as_ptr ( ) ,
196
+ v. as_mut_ptr ( ) as * mut c_char ,
197
+ v. capacity ( ) as size_t ) ,
198
+ None => libc:: readlink ( cstr. as_ptr ( ) ,
199
+ v. as_mut_ptr ( ) as * mut c_char ,
200
+ v. capacity ( ) as size_t ) ,
201
+ }
189
202
}
190
- }
203
+ } )
191
204
}
192
205
193
- pub fn readlink < P : ?Sized + NixPath > ( path : & P ) -> Result < OsString > {
206
+ fn inner_readlink < P : ?Sized + NixPath > ( dirfd : Option < RawFd > , path : & P )
207
+ -> Result < OsString > {
194
208
let mut v = Vec :: with_capacity ( libc:: PATH_MAX as usize ) ;
195
- let res = path. with_nix_path ( |cstr| {
196
- unsafe { libc:: readlink ( cstr. as_ptr ( ) , v. as_mut_ptr ( ) as * mut c_char , v. capacity ( ) as size_t ) }
197
- } ) ?;
198
-
199
- wrap_readlink_result ( & mut v, res)
209
+ // simple case: result is strictly less than `PATH_MAX`
210
+ let res = readlink_maybe_at ( dirfd, path, & mut v) ?;
211
+ let len = Errno :: result ( res) ?;
212
+ debug_assert ! ( len >= 0 ) ;
213
+ if ( len as usize ) < v. capacity ( ) {
214
+ return wrap_readlink_result ( v, res) ;
215
+ }
216
+ // Uh oh, the result is too long...
217
+ // Let's try to ask lstat how many bytes to allocate.
218
+ let reported_size = super :: sys:: stat:: lstat ( path)
219
+ . and_then ( |x| Ok ( x. st_size ) ) . unwrap_or ( 0 ) ;
220
+ let mut try_size = if reported_size > 0 {
221
+ // Note: even if `lstat`'s apparently valid answer turns out to be
222
+ // wrong, we will still read the full symlink no matter what.
223
+ reported_size as usize + 1
224
+ } else {
225
+ // If lstat doesn't cooperate, or reports an error, be a little less
226
+ // precise.
227
+ ( libc:: PATH_MAX as usize ) . max ( 128 ) << 1
228
+ } ;
229
+ loop {
230
+ v. reserve_exact ( try_size) ;
231
+ let res = readlink_maybe_at ( dirfd, path, & mut v) ?;
232
+ let len = Errno :: result ( res) ?;
233
+ debug_assert ! ( len >= 0 ) ;
234
+ if ( len as usize ) < v. capacity ( ) {
235
+ break wrap_readlink_result ( v, res) ;
236
+ }
237
+ else {
238
+ // Ugh! Still not big enough!
239
+ match try_size. checked_shl ( 1 ) {
240
+ Some ( next_size) => try_size = next_size,
241
+ // It's absurd that this would happen, but handle it sanely
242
+ // anyway.
243
+ None => break Err ( super :: Error :: Sys ( Errno :: ENAMETOOLONG ) )
244
+ }
245
+ }
246
+ }
200
247
}
201
248
249
+ pub fn readlink < P : ?Sized + NixPath > ( path : & P ) -> Result < OsString > {
250
+ inner_readlink ( None , path)
251
+ }
202
252
203
- pub fn readlinkat < P : ?Sized + NixPath > ( dirfd : RawFd , path : & P ) -> Result < OsString > {
204
- let mut v = Vec :: with_capacity ( libc:: PATH_MAX as usize ) ;
205
- let res = path. with_nix_path ( |cstr| {
206
- unsafe { libc:: readlinkat ( dirfd, cstr. as_ptr ( ) , v. as_mut_ptr ( ) as * mut c_char , v. capacity ( ) as size_t ) }
207
- } ) ?;
208
253
209
- wrap_readlink_result ( & mut v, res)
254
+ pub fn readlinkat < P : ?Sized + NixPath > ( dirfd : RawFd , path : & P )
255
+ -> Result < OsString > {
256
+ inner_readlink ( Some ( dirfd) , path)
210
257
}
211
258
212
259
/// Computes the raw fd consumed by a function of the form `*at`.
0 commit comments