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

Commit f3cf59d

Browse files
committed
Start hacking towards a test case
1 parent ddae59e commit f3cf59d

File tree

8 files changed

+227
-7
lines changed

8 files changed

+227
-7
lines changed

build.rs

+3
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

+1
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

+3
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

+97-7
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1+
use core::default::Default;
12
use core::ops::Range;
23

4+
use alloc::boxed::Box;
5+
36
use crate::bindings;
47
use crate::c_types;
5-
use crate::error;
8+
use crate::error::{Error, KernelResult};
9+
use crate::user_ptr::{UserSlicePtr, UserSlicePtrWriter};
610

711
pub struct DeviceNumberRegion {
812
dev: bindings::dev_t,
913
count: usize,
1014
}
1115

1216
impl DeviceNumberRegion {
13-
pub fn allocate(
14-
minors: Range<usize>,
15-
name: &'static str,
16-
) -> error::KernelResult<DeviceNumberRegion> {
17+
pub fn allocate(minors: Range<usize>, name: &'static str) -> KernelResult<DeviceNumberRegion> {
1718
if !name.ends_with('\x00') {
18-
return Err(error::Error::EINVAL);
19+
return Err(Error::EINVAL);
1920
}
2021

2122
let count = minors.end - minors.start;
@@ -29,10 +30,32 @@ impl DeviceNumberRegion {
2930
)
3031
};
3132
if res != 0 {
32-
return Err(error::Error::from_kernel_errno(res));
33+
return Err(Error::from_kernel_errno(res));
3334
}
3435
return Ok(DeviceNumberRegion { dev, count });
3536
}
37+
38+
pub fn register_device<T: FileOperations>(&mut self) -> KernelResult<DeviceRegistration> {
39+
let cdev = unsafe { bindings::cdev_alloc() };
40+
if cdev.is_null() {
41+
return Err(Error::ENOMEM);
42+
}
43+
unsafe {
44+
(*cdev).owner = &mut bindings::__this_module;
45+
(*cdev).ops = &T::VTABLE.0 as *const bindings::file_operations;
46+
}
47+
let cdev = DeviceRegistration {
48+
cdev,
49+
_registration: self,
50+
};
51+
// TODO: Need to handle multiple register_device calls by going through the devs and
52+
// erroring if we run out.
53+
let rc = unsafe { bindings::cdev_add(cdev.cdev, self.dev, 1) };
54+
if rc != 0 {
55+
return Err(Error::from_kernel_errno(rc));
56+
}
57+
return Ok(cdev);
58+
}
3659
}
3760

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

src/user_ptr.rs

+4
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

+11
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

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

tests/chrdev/tests.rs

+59
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)