Skip to content

Commit 1537b63

Browse files
bors[bot]bachp
andcommitted
Merge #930
930: Add wrapper for linux kernel module loading r=Susurrus a=bachp - init_module and finit_module to load kernel modules - delete_module to unload kernel modules Co-authored-by: Pascal Bach <[email protected]>
2 parents d302e8d + a7fea44 commit 1537b63

18 files changed

+368
-70
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1414
([#923](https://github.com/nix-rust/nix/pull/923))
1515
- Added a `dir` module for reading directories (wraps `fdopendir`, `readdir`, and `rewinddir`).
1616
([#916](https://github.com/nix-rust/nix/pull/916))
17+
- Added `kmod` module that allows loading and unloading kernel modules on Linux.
18+
([#930](https://github.com/nix-rust/nix/pull/930))
1719

1820
### Changed
1921
- Increased required Rust version to 1.22.1/

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ cc = "1"
2323
[dev-dependencies]
2424
bytes = "0.4.8"
2525
lazy_static = "1"
26-
rand = "0.4"
26+
rand = "0.5"
2727
tempfile = "3"
2828

2929
[target.'cfg(target_os = "freebsd")'.dev-dependencies]

src/kmod.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! Load and unload kernel modules.
2+
//!
3+
//! For more details see
4+
5+
use libc;
6+
use std::ffi::CStr;
7+
use std::os::unix::io::AsRawFd;
8+
9+
use errno::Errno;
10+
use Result;
11+
12+
/// Loads a kernel module from a buffer.
13+
///
14+
/// It loads an ELF image into kernel space,
15+
/// performs any necessary symbol relocations,
16+
/// initializes module parameters to values provided by the caller,
17+
/// and then runs the module's init function.
18+
///
19+
/// This function requires `CAP_SYS_MODULE` privilege.
20+
///
21+
/// The `module_image` argument points to a buffer containing the binary image
22+
/// to be loaded. The buffer should contain a valid ELF image
23+
/// built for the running kernel.
24+
///
25+
/// The `param_values` argument is a string containing space-delimited specifications
26+
/// of the values for module parameters.
27+
/// Each of the parameter specifications has the form:
28+
///
29+
/// `name[=value[,value...]]`
30+
///
31+
/// # Example
32+
///
33+
/// ```no_run
34+
/// use std::fs::File;
35+
/// use std::io::Read;
36+
/// use std::ffi::CString;
37+
/// use nix::kmod::init_module;
38+
///
39+
/// let mut f = File::open("mykernel.ko").unwrap();
40+
/// let mut contents: Vec<u8> = Vec::new();
41+
/// f.read_to_end(&mut contents).unwrap();
42+
/// init_module(&mut contents, &CString::new("who=Rust when=Now,12").unwrap()).unwrap();
43+
/// ```
44+
///
45+
/// See [`man init_module(2)`](http://man7.org/linux/man-pages/man2/init_module.2.html) for more information.
46+
pub fn init_module(module_image: &[u8], param_values: &CStr) -> Result<()> {
47+
let res = unsafe {
48+
libc::syscall(
49+
libc::SYS_init_module,
50+
module_image.as_ptr(),
51+
module_image.len(),
52+
param_values.as_ptr(),
53+
)
54+
};
55+
56+
Errno::result(res).map(drop)
57+
}
58+
59+
libc_bitflags!(
60+
/// Flags used by the `finit_module` function.
61+
pub struct ModuleInitFlags: libc::c_uint {
62+
/// Ignore symbol version hashes.
63+
MODULE_INIT_IGNORE_MODVERSIONS;
64+
/// Ignore kernel version magic.
65+
MODULE_INIT_IGNORE_VERMAGIC;
66+
}
67+
);
68+
69+
/// Loads a kernel module from a given file descriptor.
70+
///
71+
/// # Example
72+
///
73+
/// ```no_run
74+
/// use std::fs::File;
75+
/// use std::ffi::CString;
76+
/// use nix::kmod::{finit_module, ModuleInitFlags};
77+
///
78+
/// let f = File::open("mymod.ko").unwrap();
79+
/// finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()).unwrap();
80+
/// ```
81+
///
82+
/// See [`man init_module(2)`](http://man7.org/linux/man-pages/man2/init_module.2.html) for more information.
83+
pub fn finit_module<T: AsRawFd>(fd: &T, param_values: &CStr, flags: ModuleInitFlags) -> Result<()> {
84+
let res = unsafe {
85+
libc::syscall(
86+
libc::SYS_finit_module,
87+
fd.as_raw_fd(),
88+
param_values.as_ptr(),
89+
flags.bits(),
90+
)
91+
};
92+
93+
Errno::result(res).map(drop)
94+
}
95+
96+
libc_bitflags!(
97+
/// Flags used by `delete_module`.
98+
///
99+
/// See [`man delete_module(2)`](http://man7.org/linux/man-pages/man2/delete_module.2.html)
100+
/// for a detailed description how these flags work.
101+
pub struct DeleteModuleFlags: libc::c_int {
102+
O_NONBLOCK;
103+
O_TRUNC;
104+
}
105+
);
106+
107+
/// Unloads the kernel module with the given name.
108+
///
109+
/// # Example
110+
///
111+
/// ```no_run
112+
/// use std::ffi::CString;
113+
/// use nix::kmod::{delete_module, DeleteModuleFlags};
114+
///
115+
/// delete_module(&CString::new("mymod").unwrap(), DeleteModuleFlags::O_NONBLOCK).unwrap();
116+
/// ```
117+
///
118+
/// See [`man delete_module(2)`](http://man7.org/linux/man-pages/man2/delete_module.2.html) for more information.
119+
pub fn delete_module(name: &CStr, flags: DeleteModuleFlags) -> Result<()> {
120+
let res = unsafe { libc::syscall(libc::SYS_delete_module, name.as_ptr(), flags.bits()) };
121+
122+
Errno::result(res).map(drop)
123+
}

src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ pub mod fcntl;
4343
target_os = "netbsd",
4444
target_os = "openbsd"))]
4545
pub mod ifaddrs;
46-
#[cfg(any(target_os = "linux", target_os = "android"))]
46+
#[cfg(any(target_os = "android",
47+
target_os = "linux"))]
48+
pub mod kmod;
49+
#[cfg(any(target_os = "android",
50+
target_os = "linux"))]
4751
pub mod mount;
4852
#[cfg(any(target_os = "dragonfly",
4953
target_os = "freebsd",
@@ -57,7 +61,8 @@ pub mod net;
5761
pub mod poll;
5862
#[deny(missing_docs)]
5963
pub mod pty;
60-
#[cfg(any(target_os = "linux", target_os = "android"))]
64+
#[cfg(any(target_os = "android",
65+
target_os = "linux"))]
6166
pub mod sched;
6267
pub mod sys;
6368
// This can be implemented for other platforms as soon as libc

src/pty.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ pub fn grantpt(fd: &PtyMaster) -> Result<()> {
108108
/// let slave_name = unsafe { ptsname(&master_fd) }?;
109109
///
110110
/// // Try to open the slave
111-
/// # #[allow(unused_variables)]
112-
/// let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
111+
/// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
113112
/// # Ok(())
114113
/// # }
115114
/// ```

test/sys/test_aio.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,7 @@ extern fn sigfunc(_: c_int) {
441441
#[test]
442442
#[cfg_attr(any(all(target_env = "musl", target_arch = "x86_64"), target_arch = "mips", target_arch = "mips64"), ignore)]
443443
fn test_write_sigev_signal() {
444-
#[allow(unused_variables)]
445-
let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
444+
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
446445
let sa = SigAction::new(SigHandler::Handler(sigfunc),
447446
SaFlags::SA_RESETHAND,
448447
SigSet::empty());
@@ -580,8 +579,7 @@ fn test_liocb_listio_nowait() {
580579
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
581580
#[cfg_attr(any(target_arch = "mips", target_arch = "mips64", target_env = "musl"), ignore)]
582581
fn test_liocb_listio_signal() {
583-
#[allow(unused_variables)]
584-
let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
582+
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
585583
const INITIAL: &[u8] = b"abcdef123456";
586584
const WBUF: &[u8] = b"CDEF";
587585
let mut rbuf = vec![0; 4];

test/sys/test_select.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use nix::sys::select::*;
22
use nix::unistd::{pipe, write};
33
use nix::sys::signal::SigSet;
44
use nix::sys::time::{TimeSpec, TimeValLike};
5-
use std::os::unix::io::RawFd;
65

76
#[test]
87
pub fn test_pselect() {

test/sys/test_signal.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ fn test_sigprocmask_noop() {
2828

2929
#[test]
3030
fn test_sigprocmask() {
31-
#[allow(unused_variables)]
32-
let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
31+
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
3332

3433
// This needs to be a signal that rust doesn't use in the test harness.
3534
const SIGNAL: Signal = Signal::SIGCHLD;

test/sys/test_signalfd.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ fn test_signalfd() {
44
use nix::sys::signal::{self, raise, Signal, SigSet};
55

66
// Grab the mutex for altering signals so we don't interfere with other tests.
7-
#[allow(unused_variables)]
8-
let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
7+
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
98

109
// Block the SIGUSR1 signal from automatic processing for this thread
1110
let mut mask = SigSet::empty();

test/sys/test_termios.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ fn write_all(f: RawFd, buf: &[u8]) {
1919
#[test]
2020
fn test_tcgetattr_pty() {
2121
// openpty uses ptname(3) internally
22-
#[allow(unused_variables)]
23-
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
22+
let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
2423

2524
let pty = openpty(None, None).expect("openpty failed");
2625
assert!(termios::tcgetattr(pty.master).is_ok());
@@ -47,8 +46,7 @@ fn test_tcgetattr_ebadf() {
4746
#[test]
4847
fn test_output_flags() {
4948
// openpty uses ptname(3) internally
50-
#[allow(unused_variables)]
51-
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
49+
let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
5250

5351
// Open one pty to get attributes for the second one
5452
let mut termios = {
@@ -90,8 +88,7 @@ fn test_output_flags() {
9088
#[test]
9189
fn test_local_flags() {
9290
// openpty uses ptname(3) internally
93-
#[allow(unused_variables)]
94-
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
91+
let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
9592

9693
// Open one pty to get attributes for the second one
9794
let mut termios = {

test/sys/test_uio.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use nix::sys::uio::*;
22
use nix::unistd::*;
33
use rand::{thread_rng, Rng};
4+
use rand::distributions::Alphanumeric;
45
use std::{cmp, iter};
56
use std::fs::{OpenOptions};
67
use std::os::unix::io::AsRawFd;
@@ -11,7 +12,7 @@ use tempfile::{tempfile, tempdir};
1112
fn test_writev() {
1213
let mut to_write = Vec::with_capacity(16 * 128);
1314
for _ in 0..16 {
14-
let s: String = thread_rng().gen_ascii_chars().take(128).collect();
15+
let s: String = thread_rng().sample_iter(&Alphanumeric).take(128).collect();
1516
let b = s.as_bytes();
1617
to_write.extend(b.iter().cloned());
1718
}
@@ -53,7 +54,7 @@ fn test_writev() {
5354

5455
#[test]
5556
fn test_readv() {
56-
let s:String = thread_rng().gen_ascii_chars().take(128).collect();
57+
let s:String = thread_rng().sample_iter(&Alphanumeric).take(128).collect();
5758
let to_write = s.as_bytes().to_vec();
5859
let mut storage = Vec::new();
5960
let mut allocated = 0;
@@ -199,8 +200,7 @@ fn test_process_vm_readv() {
199200
use nix::sys::signal::*;
200201
use nix::sys::wait::*;
201202

202-
#[allow(unused_variables)]
203-
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
203+
let _ = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
204204

205205
// Pre-allocate memory in the child, since allocation isn't safe
206206
// post-fork (~= async-signal-safe)

test/sys/test_wait.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ use libc::_exit;
77

88
#[test]
99
fn test_wait_signal() {
10-
#[allow(unused_variables)]
11-
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
10+
let _ = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
1211

1312
// Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
1413
match fork().expect("Error: Fork Failed") {
@@ -25,8 +24,7 @@ fn test_wait_signal() {
2524

2625
#[test]
2726
fn test_wait_exit() {
28-
#[allow(unused_variables)]
29-
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
27+
let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
3028

3129
// Safe: Child only calls `_exit`, which is async-signal-safe.
3230
match fork().expect("Error: Fork Failed") {
@@ -96,8 +94,7 @@ mod ptrace {
9694

9795
#[test]
9896
fn test_wait_ptrace() {
99-
#[allow(unused_variables)]
100-
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
97+
let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
10198

10299
match fork().expect("Error: Fork Failed") {
103100
Child => ptrace_child(),

test/test.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,27 @@ extern crate libc;
99
extern crate rand;
1010
extern crate tempfile;
1111

12+
macro_rules! skip_if_not_root {
13+
($name:expr) => {
14+
use nix::unistd::Uid;
15+
use std;
16+
use std::io::Write;
17+
18+
if !Uid::current().is_root() {
19+
let stderr = std::io::stderr();
20+
let mut handle = stderr.lock();
21+
writeln!(handle, "{} requires root privileges. Skipping test.", $name).unwrap();
22+
return;
23+
}
24+
};
25+
}
26+
1227
mod sys;
1328
mod test_dir;
1429
mod test_fcntl;
30+
#[cfg(any(target_os = "android",
31+
target_os = "linux"))]
32+
mod test_kmod;
1533
#[cfg(any(target_os = "dragonfly",
1634
target_os = "freebsd",
1735
target_os = "fushsia",

test/test_kmod/hello_mod/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
obj-m += hello.o
2+
3+
all:
4+
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
5+
6+
clean:
7+
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

test/test_kmod/hello_mod/hello.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-2.0+ or MIT
3+
*/
4+
#include <linux/module.h>
5+
#include <linux/kernel.h>
6+
7+
static int number= 1;
8+
static char *who = "World";
9+
10+
module_param(number, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
11+
MODULE_PARM_DESC(myint, "Just some number");
12+
module_param(who, charp, 0000);
13+
MODULE_PARM_DESC(who, "Whot to greet");
14+
15+
int init_module(void)
16+
{
17+
printk(KERN_INFO "Hello %s (%d)!\n", who, number);
18+
return 0;
19+
}
20+
21+
void cleanup_module(void)
22+
{
23+
printk(KERN_INFO "Goodbye %s (%d)!\n", who, number);
24+
}
25+
26+
MODULE_LICENSE("Dual MIT/GPL");

0 commit comments

Comments
 (0)