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::kobject::DeviceMetadata;
7use crate::fs::sysfs::{BlockDeviceInfo, build_block_device_directory};
8use crate::mm::memory::MemoryObject;
9use crate::mm::{MemoryAccessorExt, ProtectionFlags};
10use crate::task::CurrentTask;
11use crate::vfs::buffers::{InputBuffer, OutputBuffer};
12use crate::vfs::{
13    FileObject, FileOps, FsString, NamespaceNode, SeekTarget, default_ioctl, default_seek,
14};
15use anyhow::{Context as _, Error};
16use block_client::{BufferSlice, MutableBufferSlice, RemoteBlockClientSync};
17use fidl::endpoints::ClientEnd;
18use fidl_fuchsia_hardware_block_volume::VolumeMarker;
19use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked};
20use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
21use starnix_uapi::device_type::{BLOCK_EXTENDED_MAJOR, DeviceType};
22use starnix_uapi::errors::Errno;
23use starnix_uapi::open_flags::OpenFlags;
24use starnix_uapi::user_address::UserRef;
25use starnix_uapi::{BLKGETSIZE, BLKGETSIZE64, errno, from_status_like_fdio, off_t};
26use std::collections::btree_map::BTreeMap;
27use std::sync::atomic::{AtomicU32, Ordering};
28use std::sync::{Arc, OnceLock};
29
30pub struct RemoteBlockDeviceVmo {
31    backing_memory: MemoryObject,
32    backing_memory_size: usize,
33    block_size: u32,
34}
35
36/// A block device. This can be backed by a VMO or a Fuchsia block client depending on how it was
37/// configured.
38/// TODO(https://fxbug.dev/407091711): Remove VMO-backed block devices.
39pub enum RemoteBlockDevice {
40    Vmo(Arc<RemoteBlockDeviceVmo>),
41    Fuchsia(Arc<RemoteBlockClientSync>),
42}
43
44const BLOCK_SIZE: u32 = 512;
45
46impl RemoteBlockDevice {
47    pub fn read(&self, offset: u64, buf: &mut [u8]) -> Result<(), Error> {
48        match self {
49            RemoteBlockDevice::Vmo(device) => Ok(device.backing_memory.read(buf, offset)?),
50            RemoteBlockDevice::Fuchsia(block_client) => block_client
51                .read_at(MutableBufferSlice::Memory(buf), offset as u64)
52                .context("read_at failed"),
53        }
54    }
55
56    fn new_from_vmo<L>(
57        locked: &mut Locked<L>,
58        current_task: &CurrentTask,
59        minor: u32,
60        name: &str,
61        backing_memory: MemoryObject,
62    ) -> Result<Arc<Self>, Errno>
63    where
64        L: LockEqualOrBefore<FileOpsCore>,
65    {
66        let kernel = current_task.kernel();
67        let registry = &kernel.device_registry;
68        let device_name = FsString::from(format!("remoteblk-{name}"));
69        let virtual_block_class = registry.objects.virtual_block_class();
70        let backing_memory_size = backing_memory.get_content_size() as usize;
71        let device = Arc::new(Self::Vmo(Arc::new(RemoteBlockDeviceVmo {
72            backing_memory,
73            backing_memory_size,
74            block_size: BLOCK_SIZE,
75        })));
76        let device_weak = Arc::<RemoteBlockDevice>::downgrade(&device);
77        registry.add_device(
78            locked,
79            current_task,
80            device_name.as_ref(),
81            DeviceMetadata::new(
82                device_name.clone(),
83                DeviceType::new(BLOCK_EXTENDED_MAJOR, minor),
84                DeviceMode::Block,
85            ),
86            virtual_block_class,
87            |device, dir| build_block_device_directory(device, device_weak, dir),
88        )?;
89        Ok(device)
90    }
91
92    fn new<L>(
93        locked: &mut Locked<L>,
94        current_task: &CurrentTask,
95        minor: u32,
96        name: &str,
97        block: ClientEnd<VolumeMarker>,
98    ) -> Result<Arc<Self>, Errno>
99    where
100        L: LockEqualOrBefore<FileOpsCore>,
101    {
102        let kernel = current_task.kernel();
103        let registry = &kernel.device_registry;
104        let device_name = FsString::from(name);
105        let virtual_block_class = registry.objects.virtual_block_class();
106        let block_client = RemoteBlockClientSync::new(block.into_channel().into())
107            .map_err(|status| from_status_like_fdio!(status))?;
108        let device = Arc::new(Self::Fuchsia(Arc::new(block_client)));
109        let device_weak = Arc::<RemoteBlockDevice>::downgrade(&device);
110        registry.add_device(
111            locked,
112            current_task,
113            device_name.as_ref(),
114            DeviceMetadata::new(
115                device_name.clone(),
116                DeviceType::new(BLOCK_EXTENDED_MAJOR, minor),
117                DeviceMode::Block,
118            ),
119            virtual_block_class,
120            |device, dir| build_block_device_directory(device, device_weak, dir),
121        )?;
122        Ok(device)
123    }
124
125    pub fn create_file_ops(&self) -> Box<dyn FileOps> {
126        match self {
127            RemoteBlockDevice::Vmo(device) => {
128                Box::new(VmoBlockDeviceFile { device: device.clone() })
129            }
130            RemoteBlockDevice::Fuchsia(block_client) => {
131                Box::new(RemoteBlockDeviceFile { block_client: block_client.clone() })
132            }
133        }
134    }
135}
136
137impl BlockDeviceInfo for RemoteBlockDevice {
138    fn size(&self) -> Result<usize, Errno> {
139        match self {
140            RemoteBlockDevice::Vmo(device) => Ok(device.backing_memory.get_size() as usize),
141            RemoteBlockDevice::Fuchsia(block_client) => (block_client.block_count() as usize)
142                .checked_mul(block_client.block_size() as usize)
143                .ok_or_else(|| errno!(EINVAL)),
144        }
145    }
146}
147
148struct VmoBlockDeviceFile {
149    device: Arc<RemoteBlockDeviceVmo>,
150}
151
152impl FileOps for VmoBlockDeviceFile {
153    fn has_persistent_offsets(&self) -> bool {
154        true
155    }
156
157    fn is_seekable(&self) -> bool {
158        true
159    }
160
161    // Manually implement seek, because default_eof_offset uses st_size (which is not used for block
162    // devices).
163    fn seek(
164        &self,
165        _locked: &mut Locked<FileOpsCore>,
166        _file: &FileObject,
167        _current_task: &CurrentTask,
168        current_offset: off_t,
169        target: SeekTarget,
170    ) -> Result<off_t, Errno> {
171        default_seek(current_offset, target, || {
172            self.device.backing_memory_size.try_into().map_err(|_| errno!(EINVAL))
173        })
174    }
175
176    fn read(
177        &self,
178        _locked: &mut Locked<FileOpsCore>,
179        _file: &FileObject,
180        _current_task: &CurrentTask,
181        mut offset: usize,
182        data: &mut dyn OutputBuffer,
183    ) -> Result<usize, Errno> {
184        data.write_each(&mut move |buf| {
185            let buflen = buf.len();
186            let buf = &mut buf
187                [..std::cmp::min(self.device.backing_memory_size.saturating_sub(offset), buflen)];
188            if !buf.is_empty() {
189                self.device
190                    .backing_memory
191                    .read_uninit(buf, offset as u64)
192                    .map_err(|status| from_status_like_fdio!(status))?;
193                offset = offset.checked_add(buf.len()).ok_or_else(|| errno!(EINVAL))?;
194            }
195            Ok(buf.len())
196        })
197    }
198
199    fn write(
200        &self,
201        _locked: &mut Locked<FileOpsCore>,
202        _file: &FileObject,
203        _current_task: &CurrentTask,
204        mut offset: usize,
205        data: &mut dyn InputBuffer,
206    ) -> Result<usize, Errno> {
207        data.read_each(&mut move |buf| {
208            let to_write =
209                std::cmp::min(self.device.backing_memory_size.saturating_sub(offset), buf.len());
210            self.device
211                .backing_memory
212                .write(&buf[..to_write], offset as u64)
213                .map_err(|status| from_status_like_fdio!(status))?;
214            offset = offset.checked_add(to_write).ok_or_else(|| errno!(EINVAL))?;
215            Ok(to_write)
216        })
217    }
218
219    fn sync(&self, _file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno> {
220        Ok(())
221    }
222
223    fn get_memory(
224        &self,
225        _locked: &mut Locked<FileOpsCore>,
226        _file: &FileObject,
227        _current_task: &CurrentTask,
228        requested_length: Option<usize>,
229        _prot: ProtectionFlags,
230    ) -> Result<Arc<MemoryObject>, Errno> {
231        let slice_len =
232            std::cmp::min(self.device.backing_memory_size, requested_length.unwrap_or(usize::MAX))
233                as u64;
234        self.device
235            .backing_memory
236            .create_child(zx::VmoChildOptions::SLICE, 0, slice_len)
237            .map(Arc::new)
238            .map_err(|status| from_status_like_fdio!(status))
239    }
240
241    fn ioctl(
242        &self,
243        locked: &mut Locked<Unlocked>,
244        file: &FileObject,
245        current_task: &CurrentTask,
246        request: u32,
247        arg: SyscallArg,
248    ) -> Result<SyscallResult, Errno> {
249        match request {
250            BLKGETSIZE => {
251                let user_size = UserRef::<u64>::from(arg);
252                let size =
253                    (self.device.backing_memory_size / self.device.block_size as usize) as u64;
254                current_task.write_object(user_size, &size)?;
255                Ok(SUCCESS)
256            }
257            BLKGETSIZE64 => {
258                let user_size = UserRef::<u64>::from(arg);
259                let size = self.device.backing_memory_size as u64;
260                current_task.write_object(user_size, &size)?;
261                Ok(SUCCESS)
262            }
263            _ => default_ioctl(file, locked, current_task, request, arg),
264        }
265    }
266}
267
268struct RemoteBlockDeviceFile {
269    block_client: Arc<RemoteBlockClientSync>,
270}
271
272impl RemoteBlockDeviceFile {
273    fn size(&self) -> Result<usize, Errno> {
274        (self.block_client.block_count() as usize)
275            .checked_mul(self.block_client.block_size() as usize)
276            .ok_or_else(|| errno!(EINVAL))
277    }
278}
279
280impl FileOps for RemoteBlockDeviceFile {
281    fn has_persistent_offsets(&self) -> bool {
282        true
283    }
284
285    fn is_seekable(&self) -> bool {
286        true
287    }
288
289    // Manually implement seek, because default_eof_offset uses st_size (which is not used for block
290    // devices).
291    fn seek(
292        &self,
293        _locked: &mut Locked<FileOpsCore>,
294        _file: &FileObject,
295        _current_task: &CurrentTask,
296        current_offset: off_t,
297        target: SeekTarget,
298    ) -> Result<off_t, Errno> {
299        default_seek(current_offset, target, || self.size()?.try_into().map_err(|_| errno!(EINVAL)))
300    }
301
302    fn read(
303        &self,
304        _locked: &mut Locked<FileOpsCore>,
305        _file: &FileObject,
306        _current_task: &CurrentTask,
307        mut offset: usize,
308        data: &mut dyn OutputBuffer,
309    ) -> Result<usize, Errno> {
310        let size = self.size()?;
311        let block_size = self.block_client.block_size() as usize;
312        const MAX_CHUNK_SIZE: usize = 32 * 1024; // 32KB
313
314        let mut total_read = 0;
315        while data.available() > 0 && offset < size {
316            let chunk_len = std::cmp::min(data.available(), MAX_CHUNK_SIZE);
317            let chunk_len = std::cmp::min(chunk_len, size - offset);
318            if chunk_len == 0 {
319                break;
320            }
321
322            let aligned_offset = offset - offset % block_size;
323            let end_offset = offset + chunk_len;
324            let aligned_end_offset = std::cmp::min(
325                end_offset.checked_next_multiple_of(block_size).ok_or_else(|| errno!(EINVAL))?,
326                size,
327            );
328            let aligned_data_length = aligned_end_offset - aligned_offset;
329
330            let mut read_data = vec![0u8; aligned_data_length];
331            self.block_client
332                .read_at(MutableBufferSlice::Memory(&mut read_data), aligned_offset as u64)
333                .map_err(|status| from_status_like_fdio!(status))?;
334
335            let read_offset = offset - aligned_offset;
336            let read_end = read_offset + chunk_len;
337            let bytes_written = data.write(&read_data[read_offset..read_end])?;
338
339            offset += bytes_written;
340            total_read += bytes_written;
341
342            if bytes_written < chunk_len {
343                break;
344            }
345        }
346        Ok(total_read)
347    }
348
349    fn write(
350        &self,
351        _locked: &mut Locked<FileOpsCore>,
352        _file: &FileObject,
353        _current_task: &CurrentTask,
354        mut offset: usize,
355        data: &mut dyn InputBuffer,
356    ) -> Result<usize, Errno> {
357        let size = self.size()?;
358        let block_size = self.block_client.block_size() as usize;
359        const MAX_CHUNK_SIZE: usize = 32 * 1024; // 32KB
360
361        let mut total_written = 0;
362        while data.available() > 0 && offset < size {
363            let chunk_len = std::cmp::min(data.available(), MAX_CHUNK_SIZE);
364            let chunk_len = std::cmp::min(chunk_len, size - offset);
365            if chunk_len == 0 {
366                break;
367            }
368
369            let aligned_offset = offset - offset % block_size;
370            let end_offset = offset + chunk_len;
371            let aligned_end_offset = std::cmp::min(
372                end_offset.checked_next_multiple_of(block_size).ok_or_else(|| errno!(EINVAL))?,
373                size,
374            );
375            let aligned_data_length = aligned_end_offset - aligned_offset;
376
377            let mut write_buf = vec![0u8; aligned_data_length];
378
379            // Read-Modify-Write: If the write is not block-aligned at the start or end,
380            // we need to read the existing data for the first and/or last block to preserve it.
381            let head_unaligned = offset > aligned_offset;
382            let tail_unaligned = end_offset < aligned_end_offset;
383
384            if head_unaligned {
385                self.block_client
386                    .read_at(
387                        MutableBufferSlice::Memory(&mut write_buf[..block_size]),
388                        aligned_offset as u64,
389                    )
390                    .map_err(|status| from_status_like_fdio!(status))?;
391            }
392
393            if tail_unaligned {
394                let last_block_start = aligned_data_length - block_size;
395                // If we already read the first block and it's the same as the last block, don't
396                // read again.
397                if !head_unaligned || last_block_start > 0 {
398                    self.block_client
399                        .read_at(
400                            MutableBufferSlice::Memory(&mut write_buf[last_block_start..]),
401                            (aligned_offset + last_block_start) as u64,
402                        )
403                        .map_err(|status| from_status_like_fdio!(status))?;
404                }
405            }
406
407            let write_offset = offset - aligned_offset;
408            let write_slice = &mut write_buf[write_offset..write_offset + chunk_len];
409            // SAFETY: We are writing to a buffer of u8, which is always initialized.
410            // We can safely cast &mut [u8] to &mut [MaybeUninit<u8>].
411            let write_slice_uninit = unsafe {
412                std::slice::from_raw_parts_mut(
413                    write_slice.as_mut_ptr() as *mut std::mem::MaybeUninit<u8>,
414                    write_slice.len(),
415                )
416            };
417            let bytes_read = data.read(write_slice_uninit)?;
418
419            self.block_client
420                .write_at(BufferSlice::Memory(&write_buf), aligned_offset as u64)
421                .map_err(|status| from_status_like_fdio!(status))?;
422
423            offset += bytes_read;
424            total_written += bytes_read;
425
426            if bytes_read < chunk_len {
427                break;
428            }
429        }
430        Ok(total_written)
431    }
432
433    fn sync(&self, _file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno> {
434        self.block_client.flush().map_err(|status| from_status_like_fdio!(status))
435    }
436
437    fn ioctl(
438        &self,
439        locked: &mut Locked<Unlocked>,
440        file: &FileObject,
441        current_task: &CurrentTask,
442        request: u32,
443        arg: SyscallArg,
444    ) -> Result<SyscallResult, Errno> {
445        match request {
446            BLKGETSIZE => {
447                let user_size = UserRef::<u64>::from(arg);
448                let size = self.block_client.block_count();
449                current_task.write_object(user_size, &size)?;
450                Ok(SUCCESS)
451            }
452            BLKGETSIZE64 => {
453                let user_size = UserRef::<u64>::from(arg);
454                let size = self.size()? as u64;
455                current_task.write_object(user_size, &size)?;
456                Ok(SUCCESS)
457            }
458            _ => default_ioctl(file, locked, current_task, request, arg),
459        }
460    }
461}
462
463fn open_remote_block_device(
464    _locked: &mut Locked<FileOpsCore>,
465    current_task: &CurrentTask,
466    id: DeviceType,
467    _node: &NamespaceNode,
468    _flags: OpenFlags,
469) -> Result<Box<dyn FileOps>, Errno> {
470    Ok(current_task.kernel().remote_block_device_registry.open(id.minor())?.create_file_ops())
471}
472
473pub fn remote_block_device_init(locked: &mut Locked<Unlocked>, current_task: &CurrentTask) {
474    current_task
475        .kernel()
476        .device_registry
477        .register_major(
478            locked,
479            "remote-block".into(),
480            DeviceMode::Block,
481            BLOCK_EXTENDED_MAJOR,
482            open_remote_block_device,
483        )
484        .expect("remote block device register failed.");
485}
486
487#[derive(Default)]
488pub struct RemoteBlockDeviceRegistry {
489    devices: Mutex<BTreeMap<u32, Arc<RemoteBlockDevice>>>,
490    next_minor: AtomicU32,
491    device_added_fn: OnceLock<RemoteBlockDeviceAddedFn>,
492}
493
494/// Arguments are (name, minor, device).
495pub type RemoteBlockDeviceAddedFn = Box<dyn Fn(&str, u32, &Arc<RemoteBlockDevice>) + Send + Sync>;
496
497impl RemoteBlockDeviceRegistry {
498    /// Registers a callback to be invoked for each new device.  Only one callback can be registered.
499    pub fn on_device_added(&self, callback: RemoteBlockDeviceAddedFn) {
500        self.device_added_fn.set(callback).map_err(|_| ()).expect("Callback already set");
501    }
502
503    /// Creates a new block device called `name` if absent.  Does nothing if the device already
504    /// exists.
505    pub fn create_vmo_block_device<L>(
506        &self,
507        locked: &mut Locked<L>,
508        current_task: &CurrentTask,
509        name: &str,
510        initial_size: u64,
511    ) -> Result<(), Error>
512    where
513        L: LockEqualOrBefore<FileOpsCore>,
514    {
515        let mut devices = self.devices.lock();
516        let backing_memory = MemoryObject::from(zx::Vmo::create(initial_size)?)
517            .with_zx_name(b"starnix:remote_block_device");
518        let minor = self.next_minor.fetch_add(1, Ordering::Relaxed);
519        let device =
520            RemoteBlockDevice::new_from_vmo(locked, current_task, minor, name, backing_memory)?;
521        if let Some(callback) = self.device_added_fn.get() {
522            callback(name, minor, &device);
523        }
524        devices.insert(minor, device);
525        Ok(())
526    }
527
528    pub fn create_remote_block_device<L>(
529        &self,
530        locked: &mut Locked<L>,
531        current_task: &CurrentTask,
532        name: &str,
533        block: ClientEnd<VolumeMarker>,
534    ) -> Result<(), Error>
535    where
536        L: LockEqualOrBefore<FileOpsCore>,
537    {
538        let mut devices = self.devices.lock();
539        let minor = self.next_minor.fetch_add(1, Ordering::Relaxed);
540        let device = RemoteBlockDevice::new(locked, current_task, minor, name, block)?;
541        devices.insert(minor, device);
542        Ok(())
543    }
544
545    pub fn open(&self, minor: u32) -> Result<Arc<RemoteBlockDevice>, Errno> {
546        self.devices.lock().get(&minor).ok_or_else(|| errno!(ENODEV)).cloned()
547    }
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553    use crate::testing::{anon_test_file, map_object_anywhere, spawn_kernel_and_run};
554    use crate::vfs::{SeekTarget, VecInputBuffer, VecOutputBuffer};
555    use starnix_uapi::open_flags::OpenFlags;
556    use starnix_uapi::{BLKGETSIZE, BLKGETSIZE64};
557    use vmo_backed_block_server::{VmoBackedServer, VmoBackedServerTestingExt};
558    use zerocopy::FromBytes as _;
559
560    #[::fuchsia::test]
561    async fn test_vmo_block_device_registry() {
562        spawn_kernel_and_run(async |locked, current_task| {
563            let kernel = current_task.kernel();
564            remote_block_device_init(locked, &current_task);
565            let registry = kernel.remote_block_device_registry.clone();
566
567            registry
568                .create_vmo_block_device(locked, &current_task, "test", 1024)
569                .expect("create_vmo_block_device failed.");
570
571            let device = registry.open(0).expect("open failed.");
572            let file =
573                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
574
575            let arg_addr = map_object_anywhere(locked, &current_task, &0u64);
576            let mut arg = [0u8; 8];
577
578            file.ioctl(locked, &current_task, BLKGETSIZE64, arg_addr.into()).expect("ioctl failed");
579            current_task.read_memory_to_slice(arg_addr, &mut arg).unwrap();
580            assert_eq!(u64::read_from_bytes(&arg).unwrap(), 1024);
581
582            file.ioctl(locked, &current_task, BLKGETSIZE, arg_addr.into()).expect("ioctl failed");
583            current_task.read_memory_to_slice(arg_addr, &mut arg).unwrap();
584            assert_eq!(u64::read_from_bytes(&arg).unwrap(), 2);
585
586            let mut buf = VecOutputBuffer::new(512);
587            file.read(locked, &current_task, &mut buf).expect("read failed.");
588            assert_eq!(buf.data(), &[0u8; 512]);
589
590            let mut buf = VecInputBuffer::from(vec![1u8; 512]);
591            file.seek(locked, &current_task, SeekTarget::Set(0)).expect("seek failed");
592            file.write(locked, &current_task, &mut buf).expect("write failed.");
593
594            let mut buf = VecOutputBuffer::new(512);
595            file.seek(locked, &current_task, SeekTarget::Set(0)).expect("seek failed");
596            file.read(locked, &current_task, &mut buf).expect("read failed.");
597            assert_eq!(buf.data(), &[1u8; 512]);
598        })
599        .await;
600    }
601
602    #[::fuchsia::test]
603    async fn test_vmo_read_write_past_eof() {
604        spawn_kernel_and_run(async |locked, current_task| {
605            let kernel = current_task.kernel();
606            remote_block_device_init(locked, &current_task);
607            let registry = kernel.remote_block_device_registry.clone();
608
609            registry
610                .create_vmo_block_device(locked, &current_task, "test", 1024)
611                .expect("create_vmo_block_device failed.");
612
613            let device = registry.open(0).expect("open failed.");
614            let file =
615                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
616
617            file.seek(locked, &current_task, SeekTarget::End(0)).expect("seek failed");
618            let mut buf = VecOutputBuffer::new(512);
619            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 0);
620
621            let mut buf = VecInputBuffer::from(vec![1u8; 512]);
622            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 0);
623        })
624        .await;
625    }
626
627    #[::fuchsia::test]
628    async fn test_remote_block_device_registry() {
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            let server = Arc::new(VmoBackedServer::new(2, 512, &[0u8; 1024]));
634            let (client, server_end) = fidl::endpoints::create_endpoints::<VolumeMarker>();
635            std::thread::spawn(move || {
636                let mut executor = fuchsia_async::LocalExecutor::default();
637                executor.run_singlethreaded(async move {
638                    use fidl::endpoints::RequestStream;
639                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
640                });
641            });
642
643            registry
644                .create_remote_block_device(locked, &current_task, "test", client)
645                .expect("create_remote_block_device failed.");
646
647            let device = registry.open(0).expect("open failed.");
648            let file =
649                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
650
651            let arg_addr = map_object_anywhere(locked, &current_task, &0u64);
652            let mut arg = [0u8; 8];
653
654            file.ioctl(locked, &current_task, BLKGETSIZE64, arg_addr.into()).expect("ioctl failed");
655            current_task.read_memory_to_slice(arg_addr, &mut arg).unwrap();
656            assert_eq!(u64::read_from_bytes(&arg).unwrap(), 1024);
657
658            file.ioctl(locked, &current_task, BLKGETSIZE, arg_addr.into()).expect("ioctl failed");
659            current_task.read_memory_to_slice(arg_addr, &mut arg).unwrap();
660            assert_eq!(u64::read_from_bytes(&arg).unwrap(), 2);
661
662            // Deliberately read with a non-block-aligned buffer size. These reads come from
663            // uncontrolled sources so we need to be able to handle the alignment ourselves.
664            let mut buf = VecOutputBuffer::new(256);
665            file.read(locked, &current_task, &mut buf).expect("read failed.");
666            assert_eq!(buf.data(), &[0u8; 256]);
667
668            let mut buf = VecInputBuffer::from(vec![1u8; 256]);
669            file.seek(locked, &current_task, SeekTarget::Set(0)).expect("seek failed");
670            file.write(locked, &current_task, &mut buf).expect("write failed.");
671
672            let mut buf = VecOutputBuffer::new(256);
673            file.seek(locked, &current_task, SeekTarget::Set(0)).expect("seek failed");
674            file.read(locked, &current_task, &mut buf).expect("read failed.");
675            assert_eq!(buf.data(), &[1u8; 256]);
676        })
677        .await;
678    }
679
680    #[::fuchsia::test]
681    async fn test_read_write_past_eof() {
682        spawn_kernel_and_run(async |locked, current_task| {
683            let kernel = current_task.kernel();
684            remote_block_device_init(locked, &current_task);
685            let registry = kernel.remote_block_device_registry.clone();
686            let server = Arc::new(VmoBackedServer::new(2, 512, &[0u8; 1024]));
687            let (client, server_end) = fidl::endpoints::create_endpoints::<VolumeMarker>();
688            std::thread::spawn(move || {
689                let mut executor = fuchsia_async::LocalExecutor::default();
690                executor.run_singlethreaded(async move {
691                    use fidl::endpoints::RequestStream;
692                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
693                });
694            });
695
696            registry
697                .create_remote_block_device(locked, &current_task, "test", client)
698                .expect("create_remote_block_device failed.");
699
700            let device = registry.open(0).expect("open failed.");
701            let file =
702                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
703
704            file.seek(locked, &current_task, SeekTarget::End(0)).expect("seek failed");
705            let mut buf = VecOutputBuffer::new(512);
706            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 0);
707
708            let mut buf = VecInputBuffer::from(vec![1u8; 512]);
709            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 0);
710        })
711        .await;
712    }
713
714    #[::fuchsia::test]
715    async fn test_unaligned_read_write_spanning_blocks() {
716        spawn_kernel_and_run(async |locked, current_task| {
717            let kernel = current_task.kernel();
718            remote_block_device_init(locked, &current_task);
719            let registry = kernel.remote_block_device_registry.clone();
720            // 3 blocks of 512 bytes = 1536 bytes
721            let server = Arc::new(VmoBackedServer::new(3, 512, &[0u8; 1536]));
722            let (client, server_end) = fidl::endpoints::create_endpoints::<VolumeMarker>();
723            std::thread::spawn(move || {
724                let mut executor = fuchsia_async::LocalExecutor::default();
725                executor.run_singlethreaded(async move {
726                    use fidl::endpoints::RequestStream;
727                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
728                });
729            });
730
731            registry
732                .create_remote_block_device(locked, &current_task, "test", client)
733                .expect("create_remote_block_device failed.");
734
735            let device = registry.open(0).expect("open failed.");
736            let file =
737                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
738
739            // Write spanning across block boundaries (e.g., from 510 to 1026)
740            // Start at 510 (2 bytes before end of 1st block)
741            // Write 516 bytes (2 bytes in 1st block, 512 bytes in 2nd block, 2 bytes in 3rd block)
742            let mut buf = VecInputBuffer::from(vec![0xAAu8; 516]);
743            file.seek(locked, &current_task, SeekTarget::Set(510)).expect("seek failed");
744            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 516);
745
746            // Read back the data
747            let mut buf = VecOutputBuffer::new(516);
748            file.seek(locked, &current_task, SeekTarget::Set(510)).expect("seek failed");
749            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 516);
750            assert_eq!(buf.data(), &[0xAAu8; 516]);
751
752            // Verify surrounding data is still 0
753            let mut buf = VecOutputBuffer::new(1);
754            file.seek(locked, &current_task, SeekTarget::Set(509)).expect("seek failed");
755            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 1);
756            assert_eq!(buf.data(), &[0u8]);
757
758            let mut buf = VecOutputBuffer::new(1);
759            file.seek(locked, &current_task, SeekTarget::Set(1026)).expect("seek failed");
760            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 1);
761            assert_eq!(buf.data(), &[0u8]);
762        })
763        .await;
764    }
765
766    #[::fuchsia::test]
767    async fn test_exact_eof_boundary() {
768        spawn_kernel_and_run(async |locked, current_task| {
769            let kernel = current_task.kernel();
770            remote_block_device_init(locked, &current_task);
771            let registry = kernel.remote_block_device_registry.clone();
772            // 2 blocks of 512 bytes = 1024 bytes
773            let server = Arc::new(VmoBackedServer::new(2, 512, &[0u8; 1024]));
774            let (client, server_end) = fidl::endpoints::create_endpoints::<VolumeMarker>();
775            std::thread::spawn(move || {
776                let mut executor = fuchsia_async::LocalExecutor::default();
777                executor.run_singlethreaded(async move {
778                    use fidl::endpoints::RequestStream;
779                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
780                });
781            });
782
783            registry
784                .create_remote_block_device(locked, &current_task, "test", client)
785                .expect("create_remote_block_device failed.");
786
787            let device = registry.open(0).expect("open failed.");
788            let file =
789                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
790
791            // Write ending exactly at EOF (1024)
792            // Start at 1020, write 4 bytes
793            let mut buf = VecInputBuffer::from(vec![0xBBu8; 4]);
794            file.seek(locked, &current_task, SeekTarget::Set(1020)).expect("seek failed");
795            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 4);
796
797            // Read back
798            let mut buf = VecOutputBuffer::new(4);
799            file.seek(locked, &current_task, SeekTarget::Set(1020)).expect("seek failed");
800            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 4);
801            assert_eq!(buf.data(), &[0xBBu8; 4]);
802
803            // Try to read past EOF from 1020 (request 5 bytes)
804            let mut buf = VecOutputBuffer::new(5);
805            file.seek(locked, &current_task, SeekTarget::Set(1020)).expect("seek failed");
806            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 4);
807            assert_eq!(buf.data()[..4], [0xBBu8; 4]);
808        })
809        .await;
810    }
811
812    #[::fuchsia::test]
813    async fn test_rmw_preserves_data() {
814        spawn_kernel_and_run(async |locked, current_task| {
815            let kernel = current_task.kernel();
816            remote_block_device_init(locked, &current_task);
817            let registry = kernel.remote_block_device_registry.clone();
818            // 3 blocks of 512 bytes = 1536 bytes
819            // Initialize with a known pattern (0xFF)
820            let server = Arc::new(VmoBackedServer::new(3, 512, &[0xFFu8; 1536]));
821            let (client, server_end) = fidl::endpoints::create_endpoints::<VolumeMarker>();
822            std::thread::spawn(move || {
823                let mut executor = fuchsia_async::LocalExecutor::default();
824                executor.run_singlethreaded(async move {
825                    use fidl::endpoints::RequestStream;
826                    server.serve(server_end.into_stream().cast_stream()).await.unwrap();
827                });
828            });
829
830            registry
831                .create_remote_block_device(locked, &current_task, "test", client)
832                .expect("create_remote_block_device failed.");
833
834            let device = registry.open(0).expect("open failed.");
835            let file =
836                anon_test_file(locked, &current_task, device.create_file_ops(), OpenFlags::RDWR);
837
838            // Write a small chunk in the middle of the second block (offset 600, length 100)
839            // Block 1 is 512-1024. 600 is inside.
840            // This should trigger RMW for the second block.
841            let mut buf = VecInputBuffer::from(vec![0xAAu8; 100]);
842            file.seek(locked, &current_task, SeekTarget::Set(600)).expect("seek failed");
843            assert_eq!(file.write(locked, &current_task, &mut buf).expect("write failed."), 100);
844
845            // Read back the entire second block to verify
846            let mut buf = VecOutputBuffer::new(512);
847            file.seek(locked, &current_task, SeekTarget::Set(512)).expect("seek failed");
848            assert_eq!(file.read(locked, &current_task, &mut buf).expect("read failed."), 512);
849            let data = buf.data();
850
851            // 512 to 600 (88 bytes) should be 0xFF
852            assert_eq!(&data[0..88], &[0xFFu8; 88]);
853            // 600 to 700 (100 bytes) should be 0xAA
854            assert_eq!(&data[88..188], &[0xAAu8; 100]);
855            // 700 to 1024 (324 bytes) should be 0xFF
856            assert_eq!(&data[188..512], &[0xFFu8; 324]);
857        })
858        .await;
859    }
860}