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