11//! `GcdFile`: per-file I/O against a `DispatchIO` channel.
22//!
33//! The channel is created in `DISPATCH_IO_RANDOM` mode and owns a duplicated
4- //! file descriptor (so dispatch_io can asynchronously close its own copy when
4+ //! file descriptor (so ` dispatch_io` can asynchronously close its own copy when
55//! the channel is released, independently of the `std::fs::File` we hold).
66//! Reads and writes call `dispatch_io_read` / `dispatch_io_write` with block
77//! handlers; the handlers accumulate chunks and, on `done`, send the result
@@ -14,12 +14,12 @@ use std::sync::Arc;
1414
1515use parking_lot:: Mutex ;
1616
17- use block2:: { Block , DynBlock , RcBlock } ;
17+ use block2:: { DynBlock , RcBlock } ;
1818use dispatch2:: { DispatchData , DispatchIO , DispatchIOCloseFlags , DispatchQueue , DispatchRetained } ;
1919
2020use crate :: Result ;
2121use crate :: errors:: PagedbError ;
22- use crate :: vfs:: traits:: VfsFile ;
22+ use crate :: vfs:: traits:: { VfsFile , checked_signed_file_len } ;
2323use crate :: vfs:: types:: { ReadReq , WriteReq } ;
2424
2525pub struct GcdFile {
@@ -81,6 +81,37 @@ impl GcdFile {
8181 fn fd ( & self ) -> std:: os:: unix:: io:: RawFd {
8282 self . file . as_raw_fd ( )
8383 }
84+
85+ fn checked_dispatch_offset ( offset : u64 , len : usize ) -> Result < libc:: off_t > {
86+ let last = if len > 0 {
87+ let last_delta = u64:: try_from ( len - 1 ) . map_err ( |_| {
88+ PagedbError :: Io ( std:: io:: Error :: new (
89+ std:: io:: ErrorKind :: InvalidInput ,
90+ "buffer length does not fit in u64" ,
91+ ) )
92+ } ) ?;
93+ offset. checked_add ( last_delta) . ok_or_else ( || {
94+ PagedbError :: Io ( std:: io:: Error :: new (
95+ std:: io:: ErrorKind :: InvalidInput ,
96+ "dispatch_io offset range overflow" ,
97+ ) )
98+ } ) ?
99+ } else {
100+ offset
101+ } ;
102+ libc:: off_t:: try_from ( last) . map_err ( |_| {
103+ PagedbError :: Io ( std:: io:: Error :: new (
104+ std:: io:: ErrorKind :: InvalidInput ,
105+ "dispatch_io offset range does not fit into libc::off_t" ,
106+ ) )
107+ } ) ?;
108+ offset. try_into ( ) . map_err ( |_| {
109+ PagedbError :: Io ( std:: io:: Error :: new (
110+ std:: io:: ErrorKind :: InvalidInput ,
111+ "dispatch_io offset does not fit into libc::off_t" ,
112+ ) )
113+ } )
114+ }
84115}
85116
86117impl Drop for GcdFile {
@@ -100,7 +131,7 @@ impl Drop for GcdFile {
100131fn submit_read (
101132 channel : & DispatchIO ,
102133 queue : & DispatchQueue ,
103- offset : u64 ,
134+ offset : libc :: off_t ,
104135 len : usize ,
105136 tx : tokio:: sync:: oneshot:: Sender < std:: io:: Result < Vec < u8 > > > ,
106137) {
@@ -135,22 +166,15 @@ fn submit_read(
135166 }
136167 } ) ;
137168
138- // SAFETY: transmute is from the stand-in block signature to the typedef
139- // declared by dispatch2 — the ABI is identical because `bool` and `u8`
140- // share the same one-byte ABI, and a `*mut DispatchData` is bit-identical
141- // to a `*mut c_void`.
142- let handler_ptr: * mut DynBlock < dyn Fn ( bool , * mut DispatchData , libc:: c_int ) > = unsafe {
143- std:: mem:: transmute :: <
144- * mut Block < dyn Fn ( u8 , * mut c_void , libc:: c_int ) > ,
145- * mut DynBlock < dyn Fn ( bool , * mut DispatchData , libc:: c_int ) > ,
146- > ( RcBlock :: as_ptr ( & handler) )
147- } ;
169+ // SAFETY: ABI-compatible pointer cast to the typedef declared by dispatch2.
170+ // `bool` and `u8` are one byte, while both data arguments are pointers.
171+ let handler_ptr: * mut DynBlock < dyn Fn ( bool , * mut DispatchData , libc:: c_int ) > =
172+ RcBlock :: as_ptr ( & handler) . cast :: < DynBlock < dyn Fn ( bool , * mut DispatchData , libc:: c_int ) > > ( ) ;
148173
149174 // SAFETY: channel and queue are valid (owned by the caller's `GcdFile`);
150175 // the handler block is retained by libdispatch on submission.
151176 unsafe {
152- #[ allow( clippy:: cast_possible_wrap) ]
153- channel. read ( offset as libc:: off_t , len, queue, handler_ptr) ;
177+ channel. read ( offset, len, queue, handler_ptr) ;
154178 }
155179 // libdispatch has retained the block internally; we can drop our Rc.
156180 drop ( handler) ;
@@ -162,7 +186,7 @@ fn submit_read(
162186fn submit_write (
163187 channel : & DispatchIO ,
164188 queue : & DispatchQueue ,
165- offset : u64 ,
189+ offset : libc :: off_t ,
166190 buf : & [ u8 ] ,
167191 tx : tokio:: sync:: oneshot:: Sender < std:: io:: Result < ( ) > > ,
168192) {
@@ -185,19 +209,14 @@ fn submit_write(
185209 } ,
186210 ) ;
187211
188- // SAFETY: ABI-compatible transmute; see `submit_read`.
189- let handler_ptr: * mut DynBlock < dyn Fn ( bool , * mut DispatchData , libc:: c_int ) > = unsafe {
190- std:: mem:: transmute :: <
191- * mut Block < dyn Fn ( u8 , * mut c_void , libc:: c_int ) > ,
192- * mut DynBlock < dyn Fn ( bool , * mut DispatchData , libc:: c_int ) > ,
193- > ( RcBlock :: as_ptr ( & handler) )
194- } ;
212+ // SAFETY: ABI-compatible pointer cast; see `submit_read`.
213+ let handler_ptr: * mut DynBlock < dyn Fn ( bool , * mut DispatchData , libc:: c_int ) > =
214+ RcBlock :: as_ptr ( & handler) . cast :: < DynBlock < dyn Fn ( bool , * mut DispatchData , libc:: c_int ) > > ( ) ;
195215
196216 // SAFETY: channel/queue/data all valid; libdispatch retains both the data
197217 // object and the handler block for the operation's duration.
198218 unsafe {
199- #[ allow( clippy:: cast_possible_wrap) ]
200- channel. write ( offset as libc:: off_t , & data, queue, handler_ptr) ;
219+ channel. write ( offset, & data, queue, handler_ptr) ;
201220 }
202221 drop ( handler) ;
203222 drop ( data) ;
@@ -209,6 +228,7 @@ impl VfsFile for GcdFile {
209228 return Ok ( 0 ) ;
210229 }
211230 let len = buf. len ( ) ;
231+ let offset = Self :: checked_dispatch_offset ( offset, len) ?;
212232 let ( tx, rx) = tokio:: sync:: oneshot:: channel :: < std:: io:: Result < Vec < u8 > > > ( ) ;
213233 // The handler block and its raw block pointer are `!Send`; submitting
214234 // from a synchronous helper keeps them out of this future's state
@@ -227,6 +247,10 @@ impl VfsFile for GcdFile {
227247 }
228248
229249 async fn read_at_vectored ( & self , reqs : & mut [ ReadReq < ' _ > ] ) -> Result < ( ) > {
250+ for req in reqs. iter ( ) {
251+ Self :: checked_dispatch_offset ( req. offset , req. buf . len ( ) ) ?;
252+ }
253+
230254 // dispatch_io operations are intrinsically sequential per channel
231255 // (the channel serialises ops in submission order); issuing them one
232256 // at a time matches that and keeps the bridge simple.
@@ -247,6 +271,7 @@ impl VfsFile for GcdFile {
247271 return Ok ( 0 ) ;
248272 }
249273 let len = buf. len ( ) ;
274+ let offset = Self :: checked_dispatch_offset ( offset, len) ?;
250275 let ( tx, rx) = tokio:: sync:: oneshot:: channel :: < std:: io:: Result < ( ) > > ( ) ;
251276 // `!Send` handler/data confined to a synchronous helper; see `read_at`.
252277 submit_write ( & self . channel , & self . queue , offset, buf, tx) ;
@@ -261,6 +286,9 @@ impl VfsFile for GcdFile {
261286 if !self . writable {
262287 return Err ( PagedbError :: ReadOnly ) ;
263288 }
289+ for req in reqs {
290+ Self :: checked_dispatch_offset ( req. offset , req. buf . len ( ) ) ?;
291+ }
264292 for req in reqs {
265293 self . write_at ( req. offset , req. buf ) . await ?;
266294 }
@@ -282,10 +310,10 @@ impl VfsFile for GcdFile {
282310 if !self . writable {
283311 return Err ( PagedbError :: ReadOnly ) ;
284312 }
285- // SAFETY: `fd()` valid as above; ` len` fits in `libc::off_t` on
286- // 64-bit Apple targets (`off_t` is `i64` on macOS / iOS).
287- # [ allow ( clippy :: cast_possible_wrap ) ]
288- let rc = unsafe { libc:: ftruncate ( self . fd ( ) , len as libc :: off_t ) } ;
313+ let len = checked_signed_file_len ( len, "ftruncate" ) ? ;
314+ // SAFETY: `fd()` is valid as above and `len` was checked to fit the
315+ // signed native file-offset type.
316+ let rc = unsafe { libc:: ftruncate ( self . fd ( ) , len) } ;
289317 if rc != 0 {
290318 return Err ( PagedbError :: Io ( std:: io:: Error :: last_os_error ( ) ) ) ;
291319 }
0 commit comments