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

Implement chrdev #96

Merged
merged 1 commit into from
Jul 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {}
geofft marked this conversation as resolved.
Show resolved Hide resolved

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 _);
alex marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

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