Skip to content
This repository was archived by the owner on Mar 7, 2021. It is now read-only.

Commit d9acbbf

Browse files
committed
Start hacking towards a test case
1 parent 4464a1d commit d9acbbf

File tree

8 files changed

+233
-5
lines changed

8 files changed

+233
-5
lines changed

build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use std::process::Command;
88

99
const INCLUDED_TYPES: &[&str] = &["file_system_type", "mode_t", "umode_t", "ctl_table"];
1010
const INCLUDED_FUNCTIONS: &[&str] = &[
11+
"cdev_add",
12+
"cdev_alloc",
13+
"cdev_del",
1114
"register_filesystem",
1215
"unregister_filesystem",
1316
"krealloc",

src/bindings_helper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <linux/cdev.h>
12
#include <linux/fs.h>
23
#include <linux/module.h>
34
#include <linux/slab.h>

src/c_types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pub type c_ulong = u64;
1111
pub type c_ulonglong = u64;
1212
pub type c_ushort = u16;
1313
pub type c_schar = i8;
14+
pub type c_size_t = usize;
15+
pub type c_ssize_t = isize;
16+
1417
// See explanation in rust/src/libstd/os/raw.rs
1518
#[repr(u8)]
1619
pub enum c_void {

src/chrdev.rs

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
use core::default::Default;
12
use core::ops::Range;
23

4+
use alloc::boxed::Box;
5+
use alloc::sync::Arc;
6+
37
use crate::bindings;
48
use crate::c_types;
5-
use crate::error;
9+
use crate::error::{Error, KernelResult};
10+
use crate::user_ptr::{UserSlicePtr, UserSlicePtrWriter};
611

712
pub struct DeviceNumberRegion {
813
dev: bindings::dev_t,
@@ -13,9 +18,9 @@ impl DeviceNumberRegion {
1318
pub fn allocate(
1419
minors: Range<usize>,
1520
name: &'static str,
16-
) -> error::KernelResult<DeviceNumberRegion> {
21+
) -> KernelResult<Arc<DeviceNumberRegion>> {
1722
if !name.ends_with('\x00') {
18-
return Err(error::Error::EINVAL);
23+
return Err(Error::EINVAL);
1924
}
2025

2126
let count = minors.end - minors.start;
@@ -29,9 +34,31 @@ impl DeviceNumberRegion {
2934
)
3035
};
3136
if res != 0 {
32-
return Err(error::Error::from_kernel_errno(res));
37+
return Err(Error::from_kernel_errno(res));
38+
}
39+
return Ok(Arc::new(DeviceNumberRegion { dev, count }));
40+
}
41+
42+
pub fn register_device<T: FileOperations>(self: Arc<Self>) -> KernelResult<DeviceRegistration> {
43+
let cdev = unsafe { bindings::cdev_alloc() };
44+
if cdev.is_null() {
45+
return Err(Error::ENOMEM);
46+
}
47+
unsafe {
48+
(*cdev).owner = &mut bindings::__this_module;
49+
(*cdev).ops = &T::VTABLE.0 as *const bindings::file_operations;
50+
}
51+
let cdev = DeviceRegistration {
52+
cdev,
53+
_region: Arc::clone(&self),
54+
};
55+
// TODO: Need to handle multiple register_device calls by going through the devs and
56+
// erroring if we run out.
57+
let rc = unsafe { bindings::cdev_add(cdev.cdev, self.dev, 1) };
58+
if rc != 0 {
59+
return Err(Error::from_kernel_errno(rc));
3360
}
34-
return Ok(DeviceNumberRegion { dev, count });
61+
return Ok(cdev);
3562
}
3663
}
3764

@@ -42,3 +69,72 @@ impl Drop for DeviceNumberRegion {
4269
}
4370
}
4471
}
72+
73+
pub struct DeviceRegistration {
74+
_region: Arc<DeviceNumberRegion>,
75+
cdev: *mut bindings::cdev,
76+
}
77+
78+
unsafe impl Sync for DeviceRegistration {}
79+
80+
impl Drop for DeviceRegistration {
81+
fn drop(&mut self) {
82+
unsafe {
83+
bindings::cdev_del(self.cdev);
84+
}
85+
}
86+
}
87+
88+
pub struct FileOperationsVtable(bindings::file_operations);
89+
90+
unsafe extern "C" fn open_callback<T: FileOperations>(
91+
_inode: *mut bindings::inode,
92+
file: *mut bindings::file,
93+
) -> c_types::c_int {
94+
let f = match T::open() {
95+
Ok(f) => Box::new(f),
96+
Err(e) => return e.to_kernel_errno(),
97+
};
98+
(*file).private_data = Box::into_raw(f) as *mut c_types::c_void;
99+
return 0;
100+
}
101+
102+
unsafe extern "C" fn read_callback<T: FileOperations>(
103+
file: *mut bindings::file,
104+
buf: *mut c_types::c_char,
105+
len: c_types::c_size_t,
106+
offset: *mut bindings::loff_t,
107+
) -> c_types::c_ssize_t {
108+
let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) {
109+
Ok(ptr) => ptr.writer(),
110+
Err(e) => return e.to_kernel_errno() as c_types::c_ssize_t,
111+
};
112+
let f = &*((*file).private_data as *const T);
113+
// TODO: Pass offset to read()?
114+
match f.read(&mut data) {
115+
Ok(()) => {
116+
let written = len - data.len();
117+
(*offset) += written as i64;
118+
return written as c_types::c_ssize_t;
119+
}
120+
Err(e) => return e.to_kernel_errno() as c_types::c_ssize_t,
121+
};
122+
}
123+
124+
impl FileOperationsVtable {
125+
pub fn new<T: FileOperations>() -> FileOperationsVtable {
126+
return FileOperationsVtable(bindings::file_operations {
127+
open: Some(open_callback::<T>),
128+
read: Some(read_callback::<T>),
129+
130+
..Default::default()
131+
});
132+
}
133+
}
134+
135+
pub trait FileOperations: Sync + Sized {
136+
const VTABLE: FileOperationsVtable;
137+
138+
fn open() -> KernelResult<Self>;
139+
fn read(&self, buf: &mut UserSlicePtrWriter) -> KernelResult<()>;
140+
}

src/user_ptr.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ impl UserSlicePtrReader {
125125
pub struct UserSlicePtrWriter(*mut c_types::c_void, usize);
126126

127127
impl UserSlicePtrWriter {
128+
pub fn len(&self) -> usize {
129+
return self.1;
130+
}
131+
128132
pub fn write(&mut self, data: &[u8]) -> error::KernelResult<()> {
129133
if data.len() > self.1 || data.len() > u32::MAX as usize {
130134
return Err(error::Error::EFAULT);

tests/chrdev/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "chrdev-tests"
3+
version = "0.1.0"
4+
authors = ["Alex Gaynor <[email protected]>", "Geoffrey Thomas <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
crate-type = ["staticlib"]
9+
10+
[dependencies]
11+
linux-kernel-module = { path = "../.." }

tests/chrdev/src/lib.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#![no_std]
2+
#![feature(const_str_as_bytes)]
3+
4+
use alloc::sync::Arc;
5+
6+
use linux_kernel_module;
7+
8+
struct CycleFile;
9+
10+
impl linux_kernel_module::chrdev::FileOperations for CycleFile {
11+
const VTABLE: linux_kernel_module::chrdev::FileOperationsVtable =
12+
linux_kernel_module::chrdev::FileOperationsVtable::new::<Self>();
13+
14+
fn open() -> linux_kernel_module::KernelResult<Self> {
15+
return Ok(CycleFile);
16+
}
17+
18+
fn read(
19+
&self,
20+
buf: &mut linux_kernel_module::user_ptr::UserSlicePtrWriter,
21+
) -> linux_kernel_module::KernelResult<()> {
22+
for c in b"123456789".iter().cycle().take(buf.len()) {
23+
buf.write(&[*c])?;
24+
}
25+
return Ok(());
26+
}
27+
}
28+
29+
struct ChrdevTestModule {
30+
_dev_region: Arc<linux_kernel_module::chrdev::DeviceNumberRegion>,
31+
_chrdev_registration: linux_kernel_module::chrdev::DeviceRegistration,
32+
}
33+
34+
impl linux_kernel_module::KernelModule for ChrdevTestModule {
35+
fn init() -> linux_kernel_module::KernelResult<Self> {
36+
let dev_region =
37+
linux_kernel_module::chrdev::DeviceNumberRegion::allocate(0..1, "chrdev-tests\x00")?;
38+
let chrdev_registration = dev_region.register_device::<CycleFile>()?;
39+
Ok(ChrdevTestModule {
40+
_dev_region: dev_region,
41+
_chrdev_registration: chrdev_registration,
42+
})
43+
}
44+
}
45+
46+
linux_kernel_module::kernel_module!(
47+
ChrdevTestModule,
48+
author: "Alex Gaynor and Geoffrey Thomas",
49+
description: "A module for testing character devices",
50+
license: "GPL"
51+
);

tests/chrdev/tests.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use kernel_module_tests::with_kernel_module;
2+
use std::fs;
3+
4+
fn get_device_number() -> u32 {
5+
let devices = fs::read_to_string("/proc/devices").unwrap();
6+
let dev_no_line = devices
7+
.lines()
8+
.find(|l| l.ends_with("chrdev-tests"))
9+
.unwrap();
10+
let elements = dev_no_line.rsplitn(2, " ").collect::<Vec<_>>();
11+
assert_eq!(elements.len(), 2);
12+
assert_eq!(elements[0], "chrdev-tests");
13+
return elements[1].trim().parse().unwrap();
14+
}
15+
16+
fn temporary_file_path() -> PathBuf {
17+
let p = env::temp_dir();
18+
p.push("chrdev-test-device");
19+
return p
20+
}
21+
22+
struct<'a> UnlinkOnDelete<'a> {
23+
path: &'a Path,
24+
}
25+
26+
impl Drop for UnlinkOnDrop {
27+
fn drop(&mut self) {
28+
fs::remove_file(self.path);
29+
}
30+
}
31+
32+
fn mknod(path: &Path, mode: u32, device_number: u32) -> UnlinkOnDrop {
33+
let result = libc::mknod(CString::new(p).unwrap(), mode, device_number);
34+
assert_eq(result, 0);
35+
return UnlinkOnDrop{path};
36+
}
37+
38+
#[test]
39+
fn test_mknod() {
40+
with_kernel_module(|| {
41+
let device_number = get_device_number();
42+
let _u = mknod(temporary_file_path(), 0o600, device_number);
43+
});
44+
}
45+
46+
#[test]
47+
fn test_read() {
48+
with_kernel_module(|| {
49+
let device_number = get_device_number();
50+
let p = temporary_file_path()
51+
let _u = mknod(temporary_file_path(), 0o600, device_number);
52+
53+
let f = fs::File::open(p);
54+
let data = vec![0; 12];
55+
f.read_exact(&mut data).unwrap();
56+
assert_eq(data, "123456789123")
57+
});
58+
59+
}

0 commit comments

Comments
 (0)