@@ -29,6 +29,11 @@ use std::marker::PhantomData;
2929#[ cfg( unix) ]
3030use std:: os:: fd:: RawFd ;
3131
32+ #[ cfg( unix) ]
33+ use rustix:: fd:: BorrowedFd ;
34+ #[ cfg( target_os = "linux" ) ]
35+ use rustix:: fs:: copy_file_range as rustix_copy_file_range;
36+
3237#[ cfg( unix) ]
3338use std:: os:: unix:: io:: AsRawFd ;
3439
@@ -392,6 +397,19 @@ impl FastCopy {
392397 block_size : st. st_blksize as usize ,
393398 }
394399 }
400+
401+ /// Return a borrowed file descriptor.
402+ ///
403+ /// # Safety invariant
404+ ///
405+ /// `FastCopy` only stores file descriptors obtained from open
406+ /// [`File`](std::fs::File) objects. Those `File`s are kept alive
407+ /// by the owning [`MmapLineCursor`] / output-buffer, guaranteeing
408+ /// the fd remains valid for the duration of any borrow.
409+ pub fn as_fd ( & self ) -> BorrowedFd < ' _ > {
410+ // SAFETY: self.fd is a valid file descriptor owned by a live File.
411+ unsafe { BorrowedFd :: borrow_raw ( self . fd ) }
412+ }
395413}
396414
397415#[ cfg( test) ]
@@ -774,10 +792,10 @@ impl OutputBuffer {
774792 if chunk. in_fast_copy . is_regular && self . fast_copy . is_regular {
775793 portable_copy_file_range (
776794 chunk. out_ptr ,
777- chunk. in_fast_copy . fd ,
795+ chunk. in_fast_copy . as_fd ( ) ,
778796 // Input file offset
779797 unsafe { chunk. out_ptr . offset_from ( chunk. base_ptr ) } as libc:: off_t ,
780- self . fast_copy . fd ,
798+ self . fast_copy . as_fd ( ) ,
781799 chunk. len ,
782800 // Alignment block size: the largest of the two
783801 chunk. in_fast_copy . block_size . max ( self . fast_copy . block_size ) ,
@@ -892,9 +910,9 @@ fn reliable_write(fd: i32, ptr: *const u8, len: usize) -> std::io::Result<usize>
892910#[ allow( unused_variables) ]
893911fn portable_copy_file_range (
894912 in_ptr : * const u8 ,
895- in_fd : i32 ,
913+ in_fd : BorrowedFd < ' _ > ,
896914 in_off : libc:: off_t ,
897- out_fd : i32 ,
915+ out_fd : BorrowedFd < ' _ > ,
898916 len : usize ,
899917 block_size : usize ,
900918 cover : WriteRange ,
@@ -909,7 +927,7 @@ fn portable_copy_file_range(
909927 }
910928 #[ cfg( not( all( target_os = "linux" , target_env = "gnu" ) ) ) ]
911929 {
912- reliable_write ( out_fd, in_ptr, len)
930+ reliable_write ( out_fd. as_raw_fd ( ) , in_ptr, len)
913931 }
914932}
915933
@@ -920,37 +938,34 @@ fn portable_copy_file_range(
920938#[ cfg( all( target_os = "linux" , target_env = "gnu" ) ) ]
921939fn reliable_copy_file_range (
922940 in_ptr : * const u8 ,
923- in_fd : i32 ,
941+ in_fd : BorrowedFd < ' _ > ,
924942 mut in_off : libc:: off_t ,
925- out_fd : i32 ,
943+ out_fd : BorrowedFd < ' _ > ,
926944 len : usize ,
927945) -> std:: io:: Result < usize > {
928946 let mut pending = len;
929947 while pending > 0 {
930- let ret = unsafe {
931- libc:: copy_file_range (
932- in_fd,
933- & raw mut in_off,
934- out_fd,
935- std:: ptr:: null_mut ( ) , // Use and update output offset
936- pending,
937- 0 ,
938- )
939- } ;
940- if ret < 0 {
941- let err = io:: Error :: last_os_error ( ) ;
942- return match err. raw_os_error ( ) {
943- Some ( libc:: ENOSYS ) | Some ( libc:: EOPNOTSUPP ) | Some ( libc:: EXDEV ) => {
944- // Fallback to write(2).
945- reliable_write ( out_fd, in_ptr, pending)
946- }
947- _ => Err ( err) ,
948- } ;
949- } else if ret == 0 {
950- // EOF reached
951- break ;
948+ let mut in_off_u64 = in_off as u64 ;
949+ let result: std:: io:: Result < usize > =
950+ rustix_copy_file_range ( in_fd, Some ( & mut in_off_u64) , out_fd, None , pending)
951+ . map_err ( std:: io:: Error :: from) ;
952+
953+ match result {
954+ Ok ( 0 ) => break ,
955+ Ok ( ret) => {
956+ pending -= ret;
957+ in_off = in_off_u64 as libc:: off_t ;
958+ }
959+ Err ( err) => {
960+ return match err. raw_os_error ( ) {
961+ Some ( libc:: ENOSYS ) | Some ( libc:: EOPNOTSUPP ) | Some ( libc:: EXDEV ) => {
962+ // Fallback to write(2).
963+ reliable_write ( out_fd. as_raw_fd ( ) , in_ptr, pending)
964+ }
965+ _ => Err ( err) ,
966+ } ;
967+ }
952968 }
953- pending -= ret as usize ;
954969 }
955970 Ok ( len)
956971}
@@ -962,19 +977,15 @@ fn reliable_copy_file_range(
962977#[ cfg( all( target_os = "linux" , target_env = "gnu" ) ) ]
963978fn aligned_copy_file_range (
964979 mut in_ptr : * const u8 ,
965- in_fd : i32 ,
980+ in_fd : BorrowedFd < ' _ > ,
966981 mut in_off : libc:: off_t ,
967- out_fd : i32 ,
982+ out_fd : BorrowedFd < ' _ > ,
968983 len : usize ,
969984 block_size : usize ,
970985 cover : WriteRange ,
971986) -> std:: io:: Result < usize > {
972- // 1. Get current output offset.
973- let res = unsafe { libc:: lseek ( out_fd, 0 , libc:: SEEK_CUR ) as i64 } ;
974- if res < 0 {
975- return Err ( std:: io:: Error :: last_os_error ( ) ) ;
976- }
977- let out_off = res as usize ;
987+ // Get current output offset.
988+ let out_off = rustix:: fs:: tell ( out_fd) ? as usize ;
978989 let mut pending = len;
979990
980991 // Obtain head alignment.
@@ -991,7 +1002,7 @@ fn aligned_copy_file_range(
9911002 if head_align > 0 {
9921003 // Align the two files on a block boundary.
9931004 let head_len = head_align. min ( pending) ;
994- reliable_write ( out_fd, in_ptr, head_len) ?;
1005+ reliable_write ( out_fd. as_raw_fd ( ) , in_ptr, head_len) ?;
9951006 in_ptr = unsafe { in_ptr. add ( head_len) } ;
9961007 in_off += head_len as i64 ;
9971008 pending -= head_len;
@@ -1005,7 +1016,7 @@ fn aligned_copy_file_range(
10051016 // Copy tail if needed.
10061017 if pending > 0 && cover == WriteRange :: Complete {
10071018 in_ptr = unsafe { in_ptr. add ( aligned_len) } ;
1008- pending -= reliable_write ( out_fd, in_ptr, pending) ?;
1019+ pending -= reliable_write ( out_fd. as_raw_fd ( ) , in_ptr, pending) ?;
10091020 }
10101021
10111022 Ok ( len - pending)
@@ -1038,6 +1049,8 @@ mod tests {
10381049 #[ cfg( all( target_os = "linux" , target_env = "gnu" ) ) ]
10391050 use std:: io:: { self , Write } ;
10401051 use std:: io:: { Seek , SeekFrom } ;
1052+ #[ cfg( target_os = "linux" ) ]
1053+ use std:: os:: unix:: io:: AsFd ;
10411054 use tempfile:: NamedTempFile ;
10421055 use tempfile:: tempfile;
10431056
@@ -1773,8 +1786,8 @@ mod tests {
17731786 infile. write_all ( data) . unwrap ( ) ;
17741787 infile. rewind ( ) . unwrap ( ) ;
17751788
1776- let in_fd = infile. as_raw_fd ( ) ;
1777- let out_fd = outfile. as_raw_fd ( ) ;
1789+ let in_fd = infile. as_fd ( ) ;
1790+ let out_fd = outfile. as_fd ( ) ;
17781791 let in_ptr = data. as_ptr ( ) ;
17791792
17801793 // Copy with block size 4, cover = Complete
@@ -1809,8 +1822,8 @@ mod tests {
18091822 infile. write_all ( data) . unwrap ( ) ;
18101823 infile. rewind ( ) . unwrap ( ) ;
18111824
1812- let in_fd = infile. as_raw_fd ( ) ;
1813- let out_fd = outfile. as_raw_fd ( ) ;
1825+ let in_fd = infile. as_fd ( ) ;
1826+ let out_fd = outfile. as_fd ( ) ;
18141827 let in_ptr = data. as_ptr ( ) ;
18151828
18161829 // Copy with block size 4, cover = Blocks
@@ -1828,6 +1841,94 @@ mod tests {
18281841 assert_eq ! ( & buf, b"abcdefgh" ) ;
18291842 }
18301843
1844+ #[ test]
1845+ #[ cfg( target_os = "linux" ) ]
1846+ fn test_aligned_copy_respects_existing_out_offset ( ) {
1847+ let mut infile = tempfile ( ) . unwrap ( ) ;
1848+ let mut outfile = tempfile ( ) . unwrap ( ) ;
1849+
1850+ // Write a prefix into the output file so tell() returns a non-zero offset.
1851+ let prefix = b"HEADER" ;
1852+ outfile. write_all ( prefix) . unwrap ( ) ;
1853+ outfile. flush ( ) . unwrap ( ) ;
1854+
1855+ // Input data = 12 bytes (3 blocks of 4 when block_size=4).
1856+ let data = b"abcdefghijkl" ;
1857+ infile. write_all ( data) . unwrap ( ) ;
1858+ infile. rewind ( ) . unwrap ( ) ;
1859+
1860+ let in_fd = infile. as_fd ( ) ;
1861+ let out_fd = outfile. as_fd ( ) ;
1862+ let in_ptr = data. as_ptr ( ) ;
1863+
1864+ let copied = aligned_copy_file_range (
1865+ in_ptr,
1866+ in_fd,
1867+ 0 ,
1868+ out_fd,
1869+ data. len ( ) ,
1870+ 4 ,
1871+ WriteRange :: Complete ,
1872+ )
1873+ . unwrap ( ) ;
1874+
1875+ assert_eq ! ( copied, data. len( ) ) ;
1876+
1877+ outfile. rewind ( ) . unwrap ( ) ;
1878+ let mut buf = Vec :: new ( ) ;
1879+ outfile. read_to_end ( & mut buf) . unwrap ( ) ;
1880+
1881+ assert_eq ! ( & buf, b"HEADERabcdefghijkl" ) ;
1882+ }
1883+
1884+ #[ test]
1885+ #[ cfg( target_os = "linux" ) ]
1886+ fn test_reliable_copy_file_range_rustix_path ( ) {
1887+ let mut infile = tempfile ( ) . unwrap ( ) ;
1888+ let mut outfile = tempfile ( ) . unwrap ( ) ;
1889+
1890+ let data = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;
1891+ infile. write_all ( data) . unwrap ( ) ;
1892+ infile. rewind ( ) . unwrap ( ) ;
1893+
1894+ let in_fd = infile. as_fd ( ) ;
1895+ let out_fd = outfile. as_fd ( ) ;
1896+ let in_ptr = data. as_ptr ( ) ;
1897+
1898+ // Copy a subrange starting at offset 10.
1899+ let copied = reliable_copy_file_range ( in_ptr, in_fd, 10 , out_fd, 16 ) . unwrap ( ) ;
1900+
1901+ assert_eq ! ( copied, 16 ) ;
1902+
1903+ outfile. rewind ( ) . unwrap ( ) ;
1904+ let mut buf = Vec :: new ( ) ;
1905+ outfile. read_to_end ( & mut buf) . unwrap ( ) ;
1906+ assert_eq ! ( & buf, b"ABCDEFGHIJKLMNOP" ) ;
1907+
1908+ // Now verify that copying into a file with existing data appends correctly.
1909+ let mut infile2 = tempfile ( ) . unwrap ( ) ;
1910+ let mut outfile2 = tempfile ( ) . unwrap ( ) ;
1911+ let input_data = b"abcdefghijklmnopqrstuvwxyz" ;
1912+ infile2. write_all ( input_data) . unwrap ( ) ;
1913+ infile2. rewind ( ) . unwrap ( ) ;
1914+
1915+ outfile2. write_all ( b"PRE:" ) . unwrap ( ) ;
1916+ outfile2. flush ( ) . unwrap ( ) ;
1917+
1918+ let in_fd2 = infile2. as_fd ( ) ;
1919+ let out_fd2 = outfile2. as_fd ( ) ;
1920+ let in_ptr2 = input_data. as_ptr ( ) ;
1921+
1922+ let copied2 = reliable_copy_file_range ( in_ptr2, in_fd2, 5 , out_fd2, 10 ) . unwrap ( ) ;
1923+
1924+ assert_eq ! ( copied2, 10 ) ;
1925+
1926+ outfile2. rewind ( ) . unwrap ( ) ;
1927+ let mut buf2 = Vec :: new ( ) ;
1928+ outfile2. read_to_end ( & mut buf2) . unwrap ( ) ;
1929+ assert_eq ! ( & buf2, b"PRE:fghijklmno" ) ;
1930+ }
1931+
18311932 ///////////////////////////////
18321933 // Unit tests for write_chunk()
18331934 ///////////////////////////////
0 commit comments