Skip to content

Commit c141cbf

Browse files
authored
Rollup merge of #125087 - tbu-:pr_file_stream_len, r=ChrisDenton
Optimize `Seek::stream_len` impl for `File` It uses the file metadata on Unix with a fallback for files incorrectly reported as zero-sized. It uses `GetFileSizeEx` on Windows. This reduces the number of syscalls needed for determining the file size of an open file from 3 to 1.
2 parents ccf3198 + 4b1e28b commit c141cbf

File tree

11 files changed

+90
-11
lines changed

11 files changed

+90
-11
lines changed

library/std/src/fs.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,9 +1311,39 @@ impl Write for &File {
13111311
}
13121312
#[stable(feature = "rust1", since = "1.0.0")]
13131313
impl Seek for &File {
1314+
/// Seek to an offset, in bytes in a file.
1315+
///
1316+
/// See [`Seek::seek`] docs for more info.
1317+
///
1318+
/// # Platform-specific behavior
1319+
///
1320+
/// This function currently corresponds to the `lseek64` function on Unix
1321+
/// and the `SetFilePointerEx` function on Windows. Note that this [may
1322+
/// change in the future][changes].
1323+
///
1324+
/// [changes]: io#platform-specific-behavior
13141325
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
13151326
self.inner.seek(pos)
13161327
}
1328+
1329+
/// Returns the length of this file (in bytes).
1330+
///
1331+
/// See [`Seek::stream_len`] docs for more info.
1332+
///
1333+
/// # Platform-specific behavior
1334+
///
1335+
/// This function currently corresponds to the `statx` function on Linux
1336+
/// (with fallbacks) and the `GetFileSizeEx` function on Windows. Note that
1337+
/// this [may change in the future][changes].
1338+
///
1339+
/// [changes]: io#platform-specific-behavior
1340+
fn stream_len(&mut self) -> io::Result<u64> {
1341+
if let Some(result) = self.inner.size() {
1342+
return result;
1343+
}
1344+
io::stream_len_default(self)
1345+
}
1346+
13171347
fn stream_position(&mut self) -> io::Result<u64> {
13181348
self.inner.tell()
13191349
}
@@ -1363,6 +1393,9 @@ impl Seek for File {
13631393
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
13641394
(&*self).seek(pos)
13651395
}
1396+
fn stream_len(&mut self) -> io::Result<u64> {
1397+
(&*self).stream_len()
1398+
}
13661399
fn stream_position(&mut self) -> io::Result<u64> {
13671400
(&*self).stream_position()
13681401
}
@@ -1412,6 +1445,9 @@ impl Seek for Arc<File> {
14121445
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
14131446
(&**self).seek(pos)
14141447
}
1448+
fn stream_len(&mut self) -> io::Result<u64> {
1449+
(&**self).stream_len()
1450+
}
14151451
fn stream_position(&mut self) -> io::Result<u64> {
14161452
(&**self).stream_position()
14171453
}

library/std/src/io/mod.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,7 +2028,7 @@ pub trait Seek {
20282028

20292029
/// Returns the length of this stream (in bytes).
20302030
///
2031-
/// This method is implemented using up to three seek operations. If this
2031+
/// The default implementation uses up to three seek operations. If this
20322032
/// method returns successfully, the seek position is unchanged (i.e. the
20332033
/// position before calling this method is the same as afterwards).
20342034
/// However, if this method returns an error, the seek position is
@@ -2062,16 +2062,7 @@ pub trait Seek {
20622062
/// ```
20632063
#[unstable(feature = "seek_stream_len", issue = "59359")]
20642064
fn stream_len(&mut self) -> Result<u64> {
2065-
let old_pos = self.stream_position()?;
2066-
let len = self.seek(SeekFrom::End(0))?;
2067-
2068-
// Avoid seeking a third time when we were already at the end of the
2069-
// stream. The branch is usually way cheaper than a seek operation.
2070-
if old_pos != len {
2071-
self.seek(SeekFrom::Start(old_pos))?;
2072-
}
2073-
2074-
Ok(len)
2065+
stream_len_default(self)
20752066
}
20762067

20772068
/// Returns the current seek position from the start of the stream.
@@ -2132,6 +2123,19 @@ pub trait Seek {
21322123
}
21332124
}
21342125

2126+
pub(crate) fn stream_len_default<T: Seek + ?Sized>(self_: &mut T) -> Result<u64> {
2127+
let old_pos = self_.stream_position()?;
2128+
let len = self_.seek(SeekFrom::End(0))?;
2129+
2130+
// Avoid seeking a third time when we were already at the end of the
2131+
// stream. The branch is usually way cheaper than a seek operation.
2132+
if old_pos != len {
2133+
self_.seek(SeekFrom::Start(old_pos))?;
2134+
}
2135+
2136+
Ok(len)
2137+
}
2138+
21352139
/// Enumeration of possible methods to seek within an I/O object.
21362140
///
21372141
/// It is used by the [`Seek`] trait.

library/std/src/sys/fs/hermit.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,10 @@ impl File {
422422
self.0.seek(pos)
423423
}
424424

425+
pub fn size(&self) -> Option<io::Result<u64>> {
426+
None
427+
}
428+
425429
pub fn tell(&self) -> io::Result<u64> {
426430
self.0.tell()
427431
}

library/std/src/sys/fs/solid.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,10 @@ impl File {
459459
self.tell()
460460
}
461461

462+
pub fn size(&self) -> Option<io::Result<u64>> {
463+
None
464+
}
465+
462466
pub fn tell(&self) -> io::Result<u64> {
463467
unsafe {
464468
let mut out_offset = MaybeUninit::uninit();

library/std/src/sys/fs/uefi.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ impl File {
280280
self.0
281281
}
282282

283+
pub fn size(&self) -> Option<io::Result<u64>> {
284+
self.0
285+
}
286+
283287
pub fn tell(&self) -> io::Result<u64> {
284288
self.0
285289
}

library/std/src/sys/fs/unix.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,6 +1464,15 @@ impl File {
14641464
Ok(n as u64)
14651465
}
14661466

1467+
pub fn size(&self) -> Option<io::Result<u64>> {
1468+
match self.file_attr().map(|attr| attr.size()) {
1469+
// Fall back to default implementation if the returned size is 0,
1470+
// we might be in a proc mount.
1471+
Ok(0) => None,
1472+
result => Some(result),
1473+
}
1474+
}
1475+
14671476
pub fn tell(&self) -> io::Result<u64> {
14681477
self.seek(SeekFrom::Current(0))
14691478
}

library/std/src/sys/fs/unsupported.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ impl File {
259259
self.0
260260
}
261261

262+
pub fn size(&self) -> Option<io::Result<u64>> {
263+
self.0
264+
}
265+
262266
pub fn tell(&self) -> io::Result<u64> {
263267
self.0
264268
}

library/std/src/sys/fs/wasi.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,10 @@ impl File {
516516
self.fd.seek(pos)
517517
}
518518

519+
pub fn size(&self) -> Option<io::Result<u64>> {
520+
None
521+
}
522+
519523
pub fn tell(&self) -> io::Result<u64> {
520524
self.fd.tell()
521525
}

library/std/src/sys/fs/windows.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,14 @@ impl File {
616616
Ok(newpos as u64)
617617
}
618618

619+
pub fn size(&self) -> Option<io::Result<u64>> {
620+
let mut result = 0;
621+
Some(
622+
cvt(unsafe { c::GetFileSizeEx(self.handle.as_raw_handle(), &mut result) })
623+
.map(|_| result as u64),
624+
)
625+
}
626+
619627
pub fn tell(&self) -> io::Result<u64> {
620628
self.seek(SeekFrom::Current(0))
621629
}

library/std/src/sys/pal/windows/c/bindings.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,6 +2156,7 @@ GetExitCodeProcess
21562156
GetFileAttributesW
21572157
GetFileInformationByHandle
21582158
GetFileInformationByHandleEx
2159+
GetFileSizeEx
21592160
GetFileType
21602161
GETFINALPATHNAMEBYHANDLE_FLAGS
21612162
GetFinalPathNameByHandleW

library/std/src/sys/pal/windows/c/windows_sys.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetExitCodeProcess(hprocess :
4444
windows_targets::link!("kernel32.dll" "system" fn GetFileAttributesW(lpfilename : PCWSTR) -> u32);
4545
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandle(hfile : HANDLE, lpfileinformation : *mut BY_HANDLE_FILE_INFORMATION) -> BOOL);
4646
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandleEx(hfile : HANDLE, fileinformationclass : FILE_INFO_BY_HANDLE_CLASS, lpfileinformation : *mut core::ffi::c_void, dwbuffersize : u32) -> BOOL);
47+
windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE, lpfilesize : *mut i64) -> BOOL);
4748
windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
4849
windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
4950
windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32);

0 commit comments

Comments
 (0)