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

Commit

Permalink
Support registering a FileOperations for a chrdev
Browse files Browse the repository at this point in the history
  • Loading branch information
alex committed Jul 11, 2019
1 parent 2349935 commit f82706d
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 8 deletions.
33 changes: 32 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ use bindgen;
use cc;
use shlex;

use std::env;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::Command;
use std::{env, fs};

const INCLUDED_TYPES: &[&str] = &["file_system_type", "mode_t", "umode_t", "ctl_table"];
const INCLUDED_FUNCTIONS: &[&str] = &[
"cdev_add",
"cdev_init",
"cdev_del",
"register_filesystem",
"unregister_filesystem",
"krealloc",
Expand Down Expand Up @@ -35,6 +39,7 @@ const INCLUDED_VARS: &[&str] = &[
"BINDINGS_GFP_KERNEL",
"KERN_INFO",
"VERIFY_WRITE",
"LINUX_VERSION_CODE",
];
const OPAQUE_TYPES: &[&str] = &[
// These need to be opaque because they're both packed and aligned, which rustc
Expand All @@ -44,6 +49,30 @@ const OPAQUE_TYPES: &[&str] = &[
"xregs_state",
];

fn kernel_version_code(major: u8, minor: u8, patch: u8) -> u64 {
((major as u64) << 16) | ((minor as u64) << 8) | (patch as u64)
}

fn handle_kernel_version_cfg(bindings_path: &PathBuf) {
let f = BufReader::new(fs::File::open(bindings_path).unwrap());
let mut version = None;
for line in f.lines() {
let line = line.unwrap();
if line.starts_with("pub const LINUX_VERSION_CODE") {
let mut parts = line.split(" = ");
parts.next();
let raw_version = parts.next().unwrap();
// Remove the trailing semi-colon
version = Some(raw_version[..raw_version.len() - 1].parse::<u64>().unwrap());
break;
}
}
let version = version.expect("Couldn't find kernel version");
if version >= kernel_version_code(4, 15, 0) {
println!("cargo:rustc-cfg=kernel_4_15_0_or_greataer")
}
}

fn main() {
println!("rerun-if-env-changed=KDIR");
let output = String::from_utf8(
Expand Down Expand Up @@ -90,6 +119,8 @@ fn main() {
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");

handle_kernel_version_cfg(&out_path.join("bindings.rs"));

let mut builder = cc::Build::new();
println!("cargo:rerun-if-env-changed=CLANG");
builder.compiler(env::var("CLANG").unwrap_or("clang".to_string()));
Expand Down
2 changes: 2 additions & 0 deletions src/bindings_helper.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/version.h>

// Bindgen gets confused at certain things
//
Expand Down
3 changes: 3 additions & 0 deletions src/c_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub type c_ulong = u64;
pub type c_ulonglong = u64;
pub type c_ushort = u16;
pub type c_schar = i8;
pub type c_size_t = usize;
pub type c_ssize_t = isize;

// See explanation in rust/src/libstd/os/raw.rs
#[repr(u8)]
pub enum c_void {
Expand Down
152 changes: 146 additions & 6 deletions src/chrdev.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
use core::convert::TryInto;
use core::ops::Range;
use core::{mem, ptr};

use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;

use crate::bindings;
use crate::c_types;
use crate::error;
use crate::error::{Error, KernelResult};
use crate::user_ptr::{UserSlicePtr, UserSlicePtrWriter};

pub fn builder(name: &'static str, minors: Range<u16>) -> error::KernelResult<Builder> {
pub fn builder(name: &'static str, minors: Range<u16>) -> KernelResult<Builder> {
if !name.ends_with('\x00') {
return Err(error::Error::EINVAL);
return Err(Error::EINVAL);
}

return Ok(Builder { name, minors });
return Ok(Builder {
name,
minors,
file_ops: vec![],
});
}

pub struct Builder {
name: &'static str,
minors: Range<u16>,
file_ops: Vec<&'static FileOperationsVtable>,
}

impl Builder {
pub fn build(self) -> error::KernelResult<Registration> {
pub fn register_device<T: FileOperations>(mut self) -> Builder {
if self.file_ops.len() >= self.minors.len() {
panic!("More devices registered than minor numbers allocated.")
}
self.file_ops.push(&T::VTABLE);
return self;
}

pub fn build(self) -> KernelResult<Registration> {
let mut dev: bindings::dev_t = 0;
let res = unsafe {
bindings::alloc_chrdev_region(
Expand All @@ -30,24 +49,145 @@ impl Builder {
)
};
if res != 0 {
return Err(error::Error::from_kernel_errno(res));
return Err(Error::from_kernel_errno(res));
}

// Turn this into a boxed slice immediately because the kernel stores pointers into it, and
// so that data should never be moved.
let mut cdevs = vec![unsafe { mem::zeroed() }; self.file_ops.len()].into_boxed_slice();
for (i, file_op) in self.file_ops.iter().enumerate() {
unsafe {
bindings::cdev_init(&mut cdevs[i], &file_op.0);
cdevs[i].owner = &mut bindings::__this_module;
let rc = bindings::cdev_add(&mut cdevs[i], dev + i as bindings::dev_t, 1);
if rc != 0 {
// Clean up the ones that were allocated.
for j in 0..=i {
bindings::cdev_del(&mut cdevs[j]);
}
bindings::unregister_chrdev_region(dev, self.minors.len() as _);
return Err(Error::from_kernel_errno(rc));
}
}
}

return Ok(Registration {
dev,
count: self.minors.len(),
cdevs,
});
}
}

pub struct Registration {
dev: bindings::dev_t,
count: usize,
cdevs: Box<[bindings::cdev]>,
}

// This is safe because Registration doesn't actually expose any methods.
unsafe impl Sync for Registration {}

impl Drop for Registration {
fn drop(&mut self) {
unsafe {
for dev in self.cdevs.iter_mut() {
bindings::cdev_del(dev);
}
bindings::unregister_chrdev_region(self.dev, self.count as _);
}
}
}

pub struct FileOperationsVtable(bindings::file_operations);

unsafe extern "C" fn open_callback<T: FileOperations>(
_inode: *mut bindings::inode,
file: *mut bindings::file,
) -> c_types::c_int {
let f = match T::open() {
Ok(f) => Box::new(f),
Err(e) => return e.to_kernel_errno(),
};
(*file).private_data = Box::into_raw(f) as *mut c_types::c_void;
return 0;
}

unsafe extern "C" fn read_callback<T: FileOperations>(
file: *mut bindings::file,
buf: *mut c_types::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.writer(),
Err(e) => return e.to_kernel_errno() as c_types::c_ssize_t,
};
let f = &*((*file).private_data as *const T);
// TODO: Pass offset to read()?
match f.read(&mut data) {
Ok(()) => {
let written = len - data.len();
(*offset) += written as bindings::loff_t;
return written as c_types::c_ssize_t;
}
Err(e) => return e.to_kernel_errno() as c_types::c_ssize_t,
};
}

unsafe extern "C" fn release_callback<T: FileOperations>(
_inode: *mut bindings::inode,
file: *mut bindings::file,
) -> c_types::c_int {
let ptr = mem::replace(&mut (*file).private_data, ptr::null_mut());
drop(Box::from_raw(ptr as *mut T));
return 0;
}

impl FileOperationsVtable {
pub const fn new<T: FileOperations>() -> FileOperationsVtable {
return FileOperationsVtable(bindings::file_operations {
open: Some(open_callback::<T>),
read: Some(read_callback::<T>),
release: Some(release_callback::<T>),

check_flags: None,
clone_file_range: None,
compat_ioctl: None,
copy_file_range: None,
dedupe_file_range: None,
fallocate: None,
fasync: None,
flock: None,
flush: None,
fsync: None,
get_unmapped_area: None,
iterate: None,
iterate_shared: None,
llseek: None,
lock: None,
mmap: None,
#[cfg(kernel_4_15_0_or_greataer)]
mmap_supported_flags: 0,
owner: ptr::null_mut(),
poll: None,
read_iter: None,
sendpage: None,
setfl: None,
setlease: None,
show_fdinfo: None,
splice_read: None,
splice_write: None,
unlocked_ioctl: None,
write: None,
write_iter: None,
});
}
}

pub trait FileOperations: Sync + Sized {
const VTABLE: FileOperationsVtable;

fn open() -> KernelResult<Self>;
fn read(&self, buf: &mut UserSlicePtrWriter) -> KernelResult<()>;
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![no_std]
#![feature(allocator_api, alloc_error_handler)]
#![feature(allocator_api, alloc_error_handler, const_fn)]

extern crate alloc;

Expand Down
4 changes: 4 additions & 0 deletions src/user_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ impl UserSlicePtrReader {
pub struct UserSlicePtrWriter(*mut c_types::c_void, usize);

impl UserSlicePtrWriter {
pub fn len(&self) -> usize {
return self.1;
}

pub fn write(&mut self, data: &[u8]) -> error::KernelResult<()> {
if data.len() > self.1 || data.len() > u32::MAX as usize {
return Err(error::Error::EFAULT);
Expand Down
16 changes: 16 additions & 0 deletions tests/chrdev/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "chrdev-tests"
version = "0.1.0"
authors = ["Alex Gaynor <alex.gaynor@gmail.com>", "Geoffrey Thomas <geofft@ldpreload.com>"]
edition = "2018"

[lib]
crate-type = ["staticlib"]
test = false

[dependencies]
linux-kernel-module = { path = "../.." }

[dev-dependencies]
kernel-module-testlib = { path = "../../testlib" }
libc = "0.2.58"
47 changes: 47 additions & 0 deletions tests/chrdev/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#![no_std]
#![feature(const_str_as_bytes)]

use linux_kernel_module;

struct CycleFile;

impl linux_kernel_module::chrdev::FileOperations for CycleFile {
const VTABLE: linux_kernel_module::chrdev::FileOperationsVtable =
linux_kernel_module::chrdev::FileOperationsVtable::new::<Self>();

fn open() -> linux_kernel_module::KernelResult<Self> {
return Ok(CycleFile);
}

fn read(
&self,
buf: &mut linux_kernel_module::user_ptr::UserSlicePtrWriter,
) -> linux_kernel_module::KernelResult<()> {
for c in b"123456789".iter().cycle().take(buf.len()) {
buf.write(&[*c])?;
}
return Ok(());
}
}

struct ChrdevTestModule {
_chrdev_registration: linux_kernel_module::chrdev::Registration,
}

impl linux_kernel_module::KernelModule for ChrdevTestModule {
fn init() -> linux_kernel_module::KernelResult<Self> {
let chrdev_registration = linux_kernel_module::chrdev::builder("chrdev-tests\x00", 0..1)?
.register_device::<CycleFile>()
.build()?;
Ok(ChrdevTestModule {
_chrdev_registration: chrdev_registration,
})
}
}

linux_kernel_module::kernel_module!(
ChrdevTestModule,
author: "Alex Gaynor and Geoffrey Thomas",
description: "A module for testing character devices",
license: "GPL"
);
Loading

0 comments on commit f82706d

Please sign in to comment.