diff --git a/src/chrdev.rs b/src/chrdev.rs index 9398b0e3..e32cdbab 100644 --- a/src/chrdev.rs +++ b/src/chrdev.rs @@ -158,6 +158,32 @@ unsafe extern "C" fn read_callback( } } +unsafe extern "C" fn write_callback( + file: *mut bindings::file, + buf: *mut c_ctypes::c_char, + len: c_types::c_size_t, + offset: *mut bindings::loff_t, +) -> c_types::c_ssize_t { + let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) { + Ok(ptr) => ptr.reader(), + Err(e) => return e.to_kernel_errno().try_into().unwrap(), + }; + let f = &*((*file).private_data as *const T); + // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63). + // See discussion in #113 + let positive_offset = match (*offset).try_into() { + Ok(v) => v, + Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(), + }; + match f.write(&data, positive_offset) { + Ok(()) => { + let read = len - data.len(); + read.try_into().unwrap() + } + Err(e) => e.to_kernel_errno().try_into().unwrap(), + } +} + unsafe extern "C" fn release_callback( _inode: *mut bindings::inode, file: *mut bindings::file, @@ -193,6 +219,7 @@ impl FileOperationsVtable { FileOperationsVtable(bindings::file_operations { open: Some(open_callback::), read: Some(read_callback::), + write: Some(write_callback::), release: Some(release_callback::), llseek: Some(llseek_callback::), @@ -232,7 +259,6 @@ impl FileOperationsVtable { splice_read: None, splice_write: None, unlocked_ioctl: None, - write: None, write_iter: None, }) } @@ -260,6 +286,12 @@ pub trait FileOperations: Sync + Sized { Err(Error::EINVAL) } + /// Writes data from userspace o this file. Corresponds to the `write` + /// function pointer in `struct file_operations`. + fn write(&self, _buf: &mut UserSlicePtrReader, _offset: u64) -> KernelResult<()> { + Err(Error::EINVAL); + } + /// Changes the position of the file. Corresponds to the `llseek` function /// pointer in `struct file_operations`. fn seek(&self, _file: &File, _offset: SeekFrom) -> KernelResult { diff --git a/tests/chrdev/src/lib.rs b/tests/chrdev/src/lib.rs index 7c7b7059..2ac457ea 100644 --- a/tests/chrdev/src/lib.rs +++ b/tests/chrdev/src/lib.rs @@ -48,6 +48,39 @@ impl linux_kernel_module::chrdev::FileOperations for SeekFile { } } +struct WiteFile { + written: AtomUsize, +} + +impl linux_kernel_module::chrdev::FileOperations for WriteFile { + const VTABLE: linux_kernel_module::chrdev::FileOperationsVtable = + linux_kernel_module::chrdev::FileOperationsVtable::self::(); + + fn open() -> linux_kernel_module::KernelResult { + return Ok(WriteFile { data: vec![] }); + } + + fn write( + &self, + buf: &linux_kernel_module::user_ptr::UserSlicePtrReader, + offset: u64, + ) -> linux_kernel_module::KernelResult<()> { + let data = buf.read_all()?; + self.written.fetch_add(data.len(), Ordering::SeqCst); + return Ok(()); + } + + fn read( + &self, + buf: &mut linux_kernel_module::user_ptr::UserSlicePtrWriter, + offset: u64, + ) -> linux_kernel_module::KernelResult<()> { + let val = self.written.load(Ordering::SeqCst); + buf.write(val.to_string().as_bytes())?; + return Ok(()); + } +} + struct ChrdevTestModule { _chrdev_registration: linux_kernel_module::chrdev::Registration, } @@ -58,6 +91,7 @@ impl linux_kernel_module::KernelModule for ChrdevTestModule { linux_kernel_module::chrdev::builder(cstr!("chrdev-tests"), 0..2)? .register_device::() .register_device::() + .register_device::() .build()?; Ok(ChrdevTestModule { _chrdev_registration: chrdev_registration, diff --git a/tests/chrdev/tests/tests.rs b/tests/chrdev/tests/tests.rs index 16a0a4f4..62de787c 100644 --- a/tests/chrdev/tests/tests.rs +++ b/tests/chrdev/tests/tests.rs @@ -56,6 +56,7 @@ fn mknod(path: &PathBuf, major: libc::dev_t, minor: libc::dev_t) -> UnlinkOnDrop const READ_FILE_MINOR: libc::dev_t = 0; const SEEK_FILE_MINOR: libc::dev_t = 1; +const WRITE_FILE_MINOR: libc::dev_t = 2; #[test] fn test_mknod() { @@ -178,3 +179,40 @@ fn test_lseek() { ); }); } + +#[test] +fn test_write_unimplemented() { + with_kernel_module(|| { + let device_number = get_device_major_number(); + let p = temporary_file_path(); + let _u = mknod(&p, device_number, READ_FILE_MINOR); + + let mut f = fs::File::open(&p).unwrap(); + assert_eq!( + f.write(&[1, 2, 3]).unwrap_err().raw_os_error().unwrap(), + libc::EINVAL + ); + }) +} + +#[test] +fn test_write() { + with_kernel_module(|| { + let device_number = get_device_major_number(); + let p = temporary_file_path(); + let _u = mknod(&p, device_number, WRITE_FILE_MINOR); + + let mut f = fs::File::open(&p).unwrap(); + assert_eq(f.write(&[1, 2, 3]), Ok(3)); + + let buf = vec![]; + f.read_to_end(&mut buf).unwrap(); + assert_eq(buf, b"3"); + + assert_eq(f.write(&[1, 2, 3, 4, 5]), Ok(3)); + + let buf = vec![]; + f.read_to_end(&mut buf).unwrap(); + assert_eq(buf, b"8"); + }) +}