Skip to main content

starnix_core/device/
remote_block_device.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::device::DeviceMode;
6use crate::device::block::canonicalize_ioctl_request;
7use crate::device::kobject::DeviceMetadata;
8use crate::fs::sysfs::{BlockDeviceInfo, build_block_device_directory};
9use crate::mm::MemoryAccessorExt;
10use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
11use crate::task::{CurrentTask, KernelThreads, LockedAndTask};
12use crate::vfs::buffers::{InputBuffer, OutputBuffer};
13use crate::vfs::{
14    FileObject, FileOps, FsString, NamespaceNode, SeekTarget, default_ioctl, default_seek,
15};
16use anyhow::{Context as _, Error};
17use block_client::{BlockClient, BufferSlice, MutableBufferSlice, RemoteBlockClient};
18use fidl::endpoints::ClientEnd;
19use fidl_fuchsia_storage_block::BlockMarker;
20use futures::channel::oneshot;
21use futures::executor::block_on;
22use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked};
23use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
24use starnix_uapi::device_id::{BLOCK_EXTENDED_MAJOR, DeviceId};
25use starnix_uapi::errors::Errno;
26use starnix_uapi::open_flags::OpenFlags;
27use starnix_uapi::user_address::{MultiArchUserRef, UserRef};
28use starnix_uapi::{BLKGETSIZE, BLKGETSIZE64, errno, from_status_like_fdio, off_t};
29use std::collections::btree_map::BTreeMap;
30use std::sync::Arc;
31use std::sync::atomic::{AtomicU32, Ordering};
32
33/// A block device, backed by a partition hosted by Fuchsia.
34pub struct RemoteBlockDevice {
35    block_client: Arc<SyncBlockClient>,
36}
37
38impl RemoteBlockDevice {
39    pub fn read(&self, offset: u64, buf: &mut [u8]) -> Result<(), Error> {
40        self.block_client
41            .read_at(MutableBufferSlice::Memory(buf), offset as u64)
42            .context("read_at failed")
43    }
44
45    fn new<L>(
46        locked: &mut Locked<L>,
47        current_task: &CurrentTask,
48        minor: u32,
49        name: &str,
50        block: ClientEnd<BlockMarker>,
51    ) -> Result<Arc<Self>, Errno>
52    where
53        L: LockEqualOrBefore<FileOpsCore>,
54    {
55        let kernel = current_task.kernel();
56        let registry = &kernel.device_registry;
57        let device_name = FsString::from(name);
58        let virtual_block_class = registry.objects.virtual_block_class();
59        let block_client = SyncBlockClient::new(&kernel.kthreads, block)?;
60        let device = Arc::new(Self { block_client });
61        let device_weak = Arc::<RemoteBlockDevice>::downgrade(&device);
62        registry.add_device(
63            locked,
64            current_task,
65            device_name.as_ref(),
66            DeviceMetadata::new(
67                device_name.clone(),
68                DeviceId::new(BLOCK_EXTENDED_MAJOR, minor),
69                DeviceMode::Block,
70            ),
71            virtual_block_class,
72            |device, dir| build_block_device_directory(device, device_weak, dir),
73        )?;
74        Ok(device)
75    }
76
77    pub fn create_file_ops(&self) -> Box<dyn FileOps> {
78        Box::new(RemoteBlockDeviceFile { block_client: self.block_client.clone() })
79    }
80}
81
82impl BlockDeviceInfo for RemoteBlockDevice {
83    fn size(&self) -> Result<usize, Errno> {
84        (self.block_client.block_count() as usize)
85            .checked_mul(self.block_client.block_size() as usize)
86            .ok_or_else(|| errno!(EINVAL))
87    }
88}
89
90pub struct SyncBlockClient {
91    client: Arc<RemoteBlockClient>,
92    terminate_tx: Option<oneshot::Sender<()>>,
93}
94
95impl SyncBlockClient {
96    fn new(kthreads: &KernelThreads, block: ClientEnd<BlockMarker>) -> Result<Arc<Self>, Errno> {
97        let (init_tx, init_rx) = std::sync::mpsc::channel();
98        let (terminate_tx, terminate_rx) = oneshot::channel();
99
100        // Spawn a thread to run the executor.
101        let closure = move |_: LockedAndTask<'_>| async move {
102            let proxy = block.into_proxy();
103            match RemoteBlockClient::new(proxy).await {
104                Ok(client) => {
105                    let _ = init_tx.send(Ok(Arc::new(Self {
106                        client: Arc::new(client),
107                        terminate_tx: Some(terminate_tx),
108                    })));
109                }
110                Err(e) => {
111                    let _ = init_tx.send(Err(e));
112                    return;
113                }
114            }
115            // RemoteBlockClient::new() spawns a future on this closure's executor to handle the
116            // block fifo. This keeps the executor alive and handling the fifo until the client is
117            // dropped.
118            let _ = terminate_rx.await;
119        };
120
121        let req = SpawnRequestBuilder::new()
122            .with_debug_name("remote-block-client")
123            .with_async_closure(closure)
124            .build();
125        kthreads.spawner().spawn_from_request(req);
126
127        match init_rx.recv() {
128            Ok(Ok(client)) => Ok(client),
129            Ok(Err(status)) => Err(from_status_like_fdio!(status)),
130            Err(_) => Err(errno!(EINVAL)),
131        }
132    }
133
134    fn block_size(&self) -> u32 {
135        self.client.block_size()
136    }
137
138    fn block_count(&self) -> u64 {
139        self.client.block_count()
140    }
141
142    fn read_at(
143        &self,
144        buffer_slice: MutableBufferSlice<'_>,
145        device_offset: u64,
146    ) -> Result<(), zx::Status> {
147        // TODO(https://fxbug.dev/475530917): block_on is uninterruptible. Once there is an
148        // interruptible block_on, switch to that. For now this is okay because we expect the block
149        // to be brief in most cases.
150        block_on(self.client.read_at(buffer_slice, device_offset))
151    }
152
153    fn write_at(
154        &self,
155        buffer_slice: BufferSlice<'_>,
156        device_offset: u64,
157    ) -> Result<(), zx::Status> {
158        // TODO(https://fxbug.dev/475530917): block_on is uninterruptible. Once there is an
159        // interruptible block_on, switch to that. For now this is okay because we expect the block
160        // to be brief in most cases.
161        block_on(self.client.write_at(buffer_slice, device_offset))
162    }
163
164    fn flush(&self) -> Result<(), zx::Status> {
165        // TODO(https://fxbug.dev/475530917): block_on is uninterruptible. Once there is an
166        // interruptible block_on, switch to that. For now this is okay because we expect the block
167        // to be brief in most cases.
168        block_on(self.client.flush())
169    }
170}
171
172impl Drop for SyncBlockClient {
173    fn drop(&mut self) {
174        if let Some(tx) = self.terminate_tx.take() {
175            let _ = tx.send(());
176        }
177    }
178}
179
180struct RemoteBlockDeviceFile {
181    block_client: Arc<SyncBlockClient>,
182}
183
184impl RemoteBlockDeviceFile {
185    fn size(&self) -> Result<usize, Errno> {
186        (self.block_client.block_count() as usize)
187            .checked_mul(self.block_client.block_size() as usize)
188            .ok_or_else(|| errno!(EINVAL))
189    }
190}
191
192impl FileOps for RemoteBlockDeviceFile {
193    fn has_persistent_offsets(&self) -> bool {
194        true
195    }
196
197    fn is_seekable(&self) -> bool {
198        true
199    }
200
201    // Manually implement seek, because default_eof_offset uses st_size (which is not used for block
202    // devices).
203    fn seek(
204        &self,
205        _locked: &mut Locked<FileOpsCore>,
206        _file: &FileObject,
207        _current_task: &CurrentTask,
208        current_offset: off_t,
209        target: SeekTarget,
210    ) -> Result<off_t, Errno> {
211        default_seek(current_offset, target, || self.size()?.try_into().map_err(|_| errno!(EINVAL)))
212    }
213
214    fn read(
215        &self,
216        _locked: &mut Locked<FileOpsCore>,
217        _file: &FileObject,
218        _current_task: &CurrentTask,
219        mut offset: usize,
220        data: &mut dyn OutputBuffer,
221    ) -> Result<usize, Errno> {
222        let size = self.size()?;
223        let block_size = self.block_client.block_size() as usize;
224        const MAX_CHUNK_SIZE: usize = 32 * 1024; // 32KB
225
226        let mut total_read = 0;
227        while data.available() > 0 && offset < size {
228            let chunk_len = std::cmp::min(data.available(), MAX_CHUNK_SIZE);
229            let chunk_len = std::cmp::min(chunk_len, size - offset);
230            if chunk_len == 0 {
231                break;
232            }
233
234            let aligned_offset = offset - offset % block_size;
235            let end_offset = offset + chunk_len;
236            let aligned_end_offset = std::cmp::min(
237                end_offset.checked_next_multiple_of(block_size).ok_or_else(|| errno!(EINVAL))?,
238                size,
239            );
240            let aligned_data_length = aligned_end_offset - aligned_offset;
241
242            let mut read_data = vec![0u8; aligned_data_length];
243            self.block_client
244                .read_at(MutableBufferSlice::Memory(&mut read_data), aligned_offset as u64)
245                .map_err(|status| from_status_like_fdio!(status))?;
246
247            let read_offset = offset - aligned_offset;
248            let read_end = read_offset + chunk_len;
249            let bytes_written = data.write(&read_data[read_offset..read_end])?;
250
251            offset += bytes_written;
252            total_read += bytes_written;
253
254            if bytes_written < chunk_len {
255                break;
256            }
257        }
258        Ok(total_read)
259    }
260
261    fn write(
262        &self,
263        _locked: &mut Locked<FileOpsCore>,
264        _file: &FileObject,
265        _current_task: &CurrentTask,
266        mut offset: usize,
267        data: &mut dyn InputBuffer,
268    ) -> Result<usize, Errno> {
269        let size = self.size()?;
270        let block_size = self.block_client.block_size() as usize;
271        const MAX_CHUNK_SIZE: usize = 32 * 1024; // 32KB
272
273        let mut total_written = 0;
274        while data.available() > 0 && offset < size {
275            let chunk_len = std::cmp::min(data.available(), MAX_CHUNK_SIZE);
276            let chunk_len = std::cmp::min(chunk_len, size - offset);
277            if chunk_len == 0 {
278                break;
279            }
280
281            let aligned_offset = offset - offset % block_size;
282            let end_offset = offset + chunk_len;
283            let aligned_end_offset = std::cmp::min(
284                end_offset.checked_next_multiple_of(block_size).ok_or_else(|| errno!(EINVAL))?,
285                size,
286            );
287            let aligned_data_length = aligned_end_offset - aligned_offset;
288
289            let mut write_buf = vec![0u8; aligned_data_length];
290
291            // Read-Modify-Write: If the write is not block-aligned at the start or end,
292            // we need to read the existing data for the first and/or last block to preserve it.
293            let head_unaligned = offset > aligned_offset;
294            let tail_unaligned = end_offset < aligned_end_offset;
295
296            if head_unaligned {
297                self.block_client
298                    .read_at(
299                        MutableBufferSlice::Memory(&mut write_buf[..block_size]),
300                        aligned_offset as u64,
301                    )
302                    .map_err(|status| from_status_like_fdio!(status))?;
303            }
304
305            if tail_unaligned {
306                let last_block_start = aligned_data_length - block_size;
307                // If we already read the first block and it's the same as the last block, don't
308                // read again.
309                if !head_unaligned || last_block_start > 0 {
310                    self.block_client
311                        .read_at(
312                            MutableBufferSlice::Memory(&mut write_buf[last_block_start..]),
313                            (aligned_offset + last_block_start) as u64,
314                        )
315                        .map_err(|status| from_status_like_fdio!(status))?;
316                }
317            }
318
319            let write_offset = offset - aligned_offset;
320            let write_slice = &mut write_buf[write_offset..write_offset + chunk_len];
321            // SAFETY: We are writing to a buffer of u8, which is always initialized.
322            // We can safely cast &mut [u8] to &mut [MaybeUninit<u8>].
323            let write_slice_uninit = unsafe {
324                std::slice::from_raw_parts_mut(
325                    write_slice.as_mut_ptr() as *mut std::mem::MaybeUninit<u8>,
326                    write_slice.len(),
327                )
328            };
329            let bytes_read = data.read(write_slice_uninit)?;
330
331            self.block_client
332                .write_at(BufferSlice::Memory(&write_buf), aligned_offset as u64)
333                .map_err(|status| from_status_like_fdio!(status))?;
334
335            offset += bytes_read;
336            total_written += bytes_read;
337
338            if bytes_read < chunk_len {
339                break;
340            }
341        }
342        Ok(total_written)
343    }
344
345    fn sync(&self, _file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno> {
346        self.block_client.flush().map_err(|status| from_status_like_fdio!(status))
347    }
348
349    fn ioctl(
350        &self,
351        locked: &mut Locked<Unlocked>,
352        file: &FileObject,
353        current_task: &CurrentTask,
354        request: u32,
355        arg: SyscallArg,
356    ) -> Result<SyscallResult, Errno> {
357        match canonicalize_ioctl_request(current_task, request) {
358            BLKGETSIZE => {
359                let user_size = MultiArchUserRef::<u64, u32>::new(current_task, arg);
360                let size = self.block_client.block_count();
361                current_task.write_multi_arch_object(user_size, size)?;
362                Ok(SUCCESS)
363            }
364            BLKGETSIZE64 => {
365                let user_size = UserRef::<u64>::from(arg);
366                let size = self.size()? as u64;
367                current_task.write_object(user_size, &size)?;
368                Ok(SUCCESS)
369            }
370            _ => default_ioctl(file, locked, current_task, request, arg),
371        }
372    }
373}
374
375fn open_remote_block_device(
376    _locked: &mut Locked<FileOpsCore>,
377    current_task: &CurrentTask,
378    id: DeviceId,
379    _node: &NamespaceNode,
380    _flags: OpenFlags,
381) -> Result<Box<dyn FileOps>, Errno> {
382    Ok(current_task.kernel().remote_block_device_registry.open(id.minor())?.create_file_ops())
383}
384
385pub fn remote_block_device_init(locked: &mut Locked<Unlocked>, current_task: &CurrentTask) {
386    current_task
387        .kernel()
388        .device_registry
389        .register_major(
390            locked,
391            "remote-block".into(),
392            DeviceMode::Block,
393            BLOCK_EXTENDED_MAJOR,
394            open_remote_block_device,
395        )
396        .expect("remote block device register failed.");
397}
398
399#[derive(Default)]
400pub struct RemoteBlockDeviceRegistry {
401    devices: Mutex<BTreeMap<u32, Arc<RemoteBlockDevice>>>,
402    next_minor: AtomicU32,
403}
404
405impl RemoteBlockDeviceRegistry {
406    pub fn create_remote_block_device<L>(
407        &self,
408        locked: &mut Locked<L>,
409        current_task: &CurrentTask,
410        name: &str,
411        block: ClientEnd<BlockMarker>,
412    ) -> Result<(), Error>
413    where
414        L: LockEqualOrBefore<FileOpsCore>,
415    {
416        let mut devices = self.devices.lock();
417        let minor = self.next_minor.fetch_add(1, Ordering::Relaxed);
418        let device = RemoteBlockDevice::new(locked, current_task, minor, name, block)?;
419        devices.insert(minor, device);
420        Ok(())
421    }
422
423    pub fn open(&self, minor: u32) -> Result<Arc<RemoteBlockDevice>, Errno> {
424        self.devices.lock().get(&minor).ok_or_else(|| errno!(ENODEV)).cloned()
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431    use crate::testing::{anon_test_file, map_object_anywhere, spawn_kernel_and_run};
432    use crate::vfs::{SeekTarget, VecInputBuffer, VecOutputBuffer};
433    use starnix_uapi::open_flags::OpenFlags;
434    use starnix_uapi::{BLKGETSIZE, BLKGETSIZE64};
435    use vmo_backed_block_server::VmoBackedServer;
436    use zerocopy::FromBytes as _;
437
438    #[::fuchsia::test]
439    async fn test_remote_block_device_registry() {
440        spawn_kernel_and_run(async |locked, current_task| {
441            let kernel = current_task.kernel();
442            remote_block_device_init(locked, &current_task);
443            let registry = kernel.remote_block_device_registry.clone();
444            let server =
445                VmoBackedServer::new(2, 512, &[]).expect("Failed to create VmoBackedServer");
446            let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
447            std::thread::spawn(move || {
448                let mut executor = fuchsia_async::LocalExecutor::default();
449                executor.run_singlethreaded(async move {
450                    use fidl::endpoints::RequestStream;
451                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
452                });
453            });
454
455            registry
456                .create_remote_block_device(locked, &current_task, "test", client)
457                .expect("create_remote_block_device failed.");
458
459            let device = registry.open(0).expect("open failed.");
460            let file =
461                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
462
463            let arg_addr = map_object_anywhere(locked, &current_task, &0u64);
464            let mut arg = [0u8; 8];
465
466            file.ioctl(locked, &current_task, BLKGETSIZE64, arg_addr.into()).expect("ioctl failed");
467            current_task.read_memory_to_slice(arg_addr, &mut arg).unwrap();
468            assert_eq!(u64::read_from_bytes(&arg).unwrap(), 1024);
469
470            file.ioctl(locked, &current_task, BLKGETSIZE, arg_addr.into()).expect("ioctl failed");
471            current_task.read_memory_to_slice(arg_addr, &mut arg).unwrap();
472            assert_eq!(u64::read_from_bytes(&arg).unwrap(), 2);
473
474            // Deliberately read with a non-block-aligned buffer size. These reads come from
475            // uncontrolled sources so we need to be able to handle the alignment ourselves.
476            let mut buf = VecOutputBuffer::new(256);
477            file.read(locked, &current_task, &mut buf).expect("read failed.");
478            assert_eq!(buf.data(), &[0u8; 256]);
479
480            let mut buf = VecInputBuffer::from(vec![1u8; 256]);
481            file.seek(locked, &current_task, SeekTarget::Set(0)).expect("seek failed");
482            file.write(locked, &current_task, &mut buf).expect("write failed.");
483
484            let mut buf = VecOutputBuffer::new(256);
485            file.seek(locked, &current_task, SeekTarget::Set(0)).expect("seek failed");
486            file.read(locked, &current_task, &mut buf).expect("read failed.");
487            assert_eq!(buf.data(), &[1u8; 256]);
488        })
489        .await;
490    }
491
492    #[::fuchsia::test]
493    async fn test_read_write_past_eof() {
494        spawn_kernel_and_run(async |locked, current_task| {
495            let kernel = current_task.kernel();
496            remote_block_device_init(locked, &current_task);
497            let registry = kernel.remote_block_device_registry.clone();
498            let server =
499                VmoBackedServer::new(2, 512, &[]).expect("Failed to create VmoBackedServer");
500            let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
501            std::thread::spawn(move || {
502                let mut executor = fuchsia_async::LocalExecutor::default();
503                executor.run_singlethreaded(async move {
504                    use fidl::endpoints::RequestStream;
505                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
506                });
507            });
508
509            registry
510                .create_remote_block_device(locked, &current_task, "test", client)
511                .expect("create_remote_block_device failed.");
512
513            let device = registry.open(0).expect("open failed.");
514            let file =
515                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
516
517            file.seek(locked, &current_task, SeekTarget::End(0)).expect("seek failed");
518            let mut buf = VecOutputBuffer::new(512);
519            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 0);
520
521            let mut buf = VecInputBuffer::from(vec![1u8; 512]);
522            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 0);
523        })
524        .await;
525    }
526
527    #[::fuchsia::test]
528    async fn test_unaligned_read_write_spanning_blocks() {
529        spawn_kernel_and_run(async |locked, current_task| {
530            let kernel = current_task.kernel();
531            remote_block_device_init(locked, &current_task);
532            let registry = kernel.remote_block_device_registry.clone();
533            // 3 blocks of 512 bytes = 1536 bytes
534            let server =
535                VmoBackedServer::new(3, 512, &[]).expect("Failed to create VmoBackedServer");
536            let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
537            std::thread::spawn(move || {
538                let mut executor = fuchsia_async::LocalExecutor::default();
539                executor.run_singlethreaded(async move {
540                    use fidl::endpoints::RequestStream;
541                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
542                });
543            });
544
545            registry
546                .create_remote_block_device(locked, &current_task, "test", client)
547                .expect("create_remote_block_device failed.");
548
549            let device = registry.open(0).expect("open failed.");
550            let file =
551                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
552
553            // Write spanning across block boundaries (e.g., from 510 to 1026)
554            // Start at 510 (2 bytes before end of 1st block)
555            // Write 516 bytes (2 bytes in 1st block, 512 bytes in 2nd block, 2 bytes in 3rd block)
556            let mut buf = VecInputBuffer::from(vec![0xAAu8; 516]);
557            file.seek(locked, &current_task, SeekTarget::Set(510)).expect("seek failed");
558            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 516);
559
560            // Read back the data
561            let mut buf = VecOutputBuffer::new(516);
562            file.seek(locked, &current_task, SeekTarget::Set(510)).expect("seek failed");
563            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 516);
564            assert_eq!(buf.data(), &[0xAAu8; 516]);
565
566            // Verify surrounding data is still 0
567            let mut buf = VecOutputBuffer::new(1);
568            file.seek(locked, &current_task, SeekTarget::Set(509)).expect("seek failed");
569            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 1);
570            assert_eq!(buf.data(), &[0u8]);
571
572            let mut buf = VecOutputBuffer::new(1);
573            file.seek(locked, &current_task, SeekTarget::Set(1026)).expect("seek failed");
574            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 1);
575            assert_eq!(buf.data(), &[0u8]);
576        })
577        .await;
578    }
579
580    #[::fuchsia::test]
581    async fn test_exact_eof_boundary() {
582        spawn_kernel_and_run(async |locked, current_task| {
583            let kernel = current_task.kernel();
584            remote_block_device_init(locked, &current_task);
585            let registry = kernel.remote_block_device_registry.clone();
586            // 2 blocks of 512 bytes = 1024 bytes
587            let server =
588                VmoBackedServer::new(2, 512, &[]).expect("Failed to create VmoBackedServer");
589            let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
590            std::thread::spawn(move || {
591                let mut executor = fuchsia_async::LocalExecutor::default();
592                executor.run_singlethreaded(async move {
593                    use fidl::endpoints::RequestStream;
594                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
595                });
596            });
597
598            registry
599                .create_remote_block_device(locked, &current_task, "test", client)
600                .expect("create_remote_block_device failed.");
601
602            let device = registry.open(0).expect("open failed.");
603            let file =
604                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
605
606            // Write ending exactly at EOF (1024)
607            // Start at 1020, write 4 bytes
608            let mut buf = VecInputBuffer::from(vec![0xBBu8; 4]);
609            file.seek(locked, &current_task, SeekTarget::Set(1020)).expect("seek failed");
610            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 4);
611
612            // Read back
613            let mut buf = VecOutputBuffer::new(4);
614            file.seek(locked, &current_task, SeekTarget::Set(1020)).expect("seek failed");
615            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 4);
616            assert_eq!(buf.data(), &[0xBBu8; 4]);
617
618            // Try to read past EOF from 1020 (request 5 bytes)
619            let mut buf = VecOutputBuffer::new(5);
620            file.seek(locked, &current_task, SeekTarget::Set(1020)).expect("seek failed");
621            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 4);
622            assert_eq!(buf.data()[..4], [0xBBu8; 4]);
623        })
624        .await;
625    }
626
627    #[::fuchsia::test]
628    async fn test_rmw_preserves_data() {
629        spawn_kernel_and_run(async |locked, current_task| {
630            let kernel = current_task.kernel();
631            remote_block_device_init(locked, &current_task);
632            let registry = kernel.remote_block_device_registry.clone();
633            // 3 blocks of 512 bytes = 1536 bytes
634            // Initialize with a known pattern (0xFF)
635            let server = VmoBackedServer::new(3, 512, &[0xFFu8; 1536])
636                .expect("Failed to create VmoBackedServer");
637            let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
638            std::thread::spawn(move || {
639                let mut executor = fuchsia_async::LocalExecutor::default();
640                executor.run_singlethreaded(async move {
641                    use fidl::endpoints::RequestStream;
642                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
643                });
644            });
645
646            registry
647                .create_remote_block_device(locked, &current_task, "test", client)
648                .expect("create_remote_block_device failed.");
649
650            let device = registry.open(0).expect("open failed.");
651            let file =
652                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
653
654            // Write a small chunk in the middle of the second block (offset 600, length 100)
655            // Block 1 is 512-1024. 600 is inside.
656            // This should trigger RMW for the second block.
657            let mut buf = VecInputBuffer::from(vec![0xAAu8; 100]);
658            file.seek(locked, &current_task, SeekTarget::Set(600)).expect("seek failed");
659            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 100);
660
661            // Read back the entire second block to verify
662            let mut buf = VecOutputBuffer::new(512);
663            file.seek(locked, &current_task, SeekTarget::Set(512)).expect("seek failed");
664            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 512);
665            let data = buf.data();
666
667            // 512 to 600 (88 bytes) should be 0xFF
668            assert_eq!(&data[0..88], &[0xFFu8; 88]);
669            // 600 to 700 (100 bytes) should be 0xAA
670            assert_eq!(&data[88..188], &[0xAAu8; 100]);
671            // 700 to 1024 (324 bytes) should be 0xFF
672            assert_eq!(&data[188..512], &[0xFFu8; 324]);
673        })
674        .await;
675    }
676}