diff --git a/.github/workflows/busybox.config b/.github/workflows/busybox.config index c4175a522a3a60..81981ec17d1727 100644 --- a/.github/workflows/busybox.config +++ b/.github/workflows/busybox.config @@ -196,7 +196,7 @@ CONFIG_GZIP_FAST=0 # Coreutils # # CONFIG_BASENAME is not set -# CONFIG_CAT is not set +CONFIG_CAT=y # CONFIG_FEATURE_CATN is not set # CONFIG_FEATURE_CATV is not set # CONFIG_CHGRP is not set @@ -209,7 +209,7 @@ CONFIG_GZIP_FAST=0 # CONFIG_CP is not set # CONFIG_FEATURE_CP_LONG_OPTIONS is not set # CONFIG_FEATURE_CP_REFLINK is not set -# CONFIG_CUT is not set +CONFIG_CUT=y # CONFIG_DATE is not set # CONFIG_FEATURE_DATE_ISOFMT is not set # CONFIG_FEATURE_DATE_NANO is not set @@ -263,9 +263,9 @@ CONFIG_GZIP_FAST=0 # CONFIG_SHA512SUM is not set # CONFIG_SHA3SUM is not set # CONFIG_FEATURE_MD5_SHA1_SUM_CHECK is not set -# CONFIG_MKDIR is not set +CONFIG_MKDIR=y # CONFIG_MKFIFO is not set -# CONFIG_MKNOD is not set +CONFIG_MKNOD=y # CONFIG_MKTEMP is not set # CONFIG_MV is not set # CONFIG_NICE is not set @@ -280,7 +280,7 @@ CONFIG_GZIP_FAST=0 # CONFIG_READLINK is not set # CONFIG_FEATURE_READLINK_FOLLOW is not set # CONFIG_REALPATH is not set -# CONFIG_RM is not set +CONFIG_RM=y # CONFIG_RMDIR is not set # CONFIG_SEQ is not set # CONFIG_SHRED is not set @@ -449,7 +449,7 @@ CONFIG_FEATURE_VI_UNDO_QUEUE_MAX=0 # CONFIG_FEATURE_FIND_REGEX is not set # CONFIG_FEATURE_FIND_CONTEXT is not set # CONFIG_FEATURE_FIND_LINKS is not set -# CONFIG_GREP is not set +CONFIG_GREP=y # CONFIG_EGREP is not set # CONFIG_FGREP is not set # CONFIG_FEATURE_GREP_CONTEXT is not set @@ -635,7 +635,7 @@ CONFIG_DEFAULT_DEPMOD_FILE="modules.dep" # CONFIG_MKSWAP is not set # CONFIG_FEATURE_MKSWAP_UUID is not set # CONFIG_MORE is not set -# CONFIG_MOUNT is not set +CONFIG_MOUNT=y # CONFIG_FEATURE_MOUNT_FAKE is not set # CONFIG_FEATURE_MOUNT_VERBOSE is not set # CONFIG_FEATURE_MOUNT_HELPERS is not set diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 82d70a58cd40f7..2471e73047aebf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -304,6 +304,15 @@ jobs: grep '] rust_semaphore_c: Rust semaphore sample (in C, for comparison) (init)$' qemu-stdout.log grep '] rust_semaphore_c: Rust semaphore sample (in C, for comparison) (exit)$' qemu-stdout.log + - run: | + grep '] rust_seq_file: Rust seq_file sample (init)$' qemu-stdout.log + grep '] rust_seq_file: Rust seq_file sample (exit)$' qemu-stdout.log + if [ $(grep -c 'rust_seq_file: device opened this many times: 2' qemu-stdout.log) -ne 3 ]; then + exit 1; + else + exit 0; + fi + # Report - run: | ls -l \ diff --git a/.github/workflows/kernel-arm-debug.config b/.github/workflows/kernel-arm-debug.config index 258ff571ee4880..142774090ce646 100644 --- a/.github/workflows/kernel-arm-debug.config +++ b/.github/workflows/kernel-arm-debug.config @@ -1665,6 +1665,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_STRICT_DEVMEM is not set # diff --git a/.github/workflows/kernel-arm-release.config b/.github/workflows/kernel-arm-release.config index 8409ced66a1713..9be5bd281bddd0 100644 --- a/.github/workflows/kernel-arm-release.config +++ b/.github/workflows/kernel-arm-release.config @@ -1589,6 +1589,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_STRICT_DEVMEM is not set # diff --git a/.github/workflows/kernel-arm64-debug.config b/.github/workflows/kernel-arm64-debug.config index e5af726bf34233..1c67b93806e3fe 100644 --- a/.github/workflows/kernel-arm64-debug.config +++ b/.github/workflows/kernel-arm64-debug.config @@ -1069,7 +1069,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set # CONFIG_SYSFS is not set # CONFIG_HUGETLBFS is not set @@ -1431,6 +1431,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # # arm64 Debugging diff --git a/.github/workflows/kernel-arm64-release.config b/.github/workflows/kernel-arm64-release.config index 2476670a0b3941..5bb0ee8a4eb9a2 100644 --- a/.github/workflows/kernel-arm64-release.config +++ b/.github/workflows/kernel-arm64-release.config @@ -1064,7 +1064,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set # CONFIG_SYSFS is not set # CONFIG_HUGETLBFS is not set @@ -1349,6 +1349,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # # arm64 Debugging diff --git a/.github/workflows/kernel-ppc64le-debug.config b/.github/workflows/kernel-ppc64le-debug.config index d211d9e79b575c..93cd35f89dc280 100644 --- a/.github/workflows/kernel-ppc64le-debug.config +++ b/.github/workflows/kernel-ppc64le-debug.config @@ -1491,6 +1491,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-ppc64le-release.config b/.github/workflows/kernel-ppc64le-release.config index a74314e6cdb86f..db4dc4419f3680 100644 --- a/.github/workflows/kernel-ppc64le-release.config +++ b/.github/workflows/kernel-ppc64le-release.config @@ -1453,6 +1453,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-x86_64-debug.config b/.github/workflows/kernel-x86_64-debug.config index 4724cf56997cb5..15caaed80e7dd7 100644 --- a/.github/workflows/kernel-x86_64-debug.config +++ b/.github/workflows/kernel-x86_64-debug.config @@ -1059,7 +1059,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set CONFIG_KERNFS=y CONFIG_SYSFS=y @@ -1443,6 +1443,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-x86_64-release.config b/.github/workflows/kernel-x86_64-release.config index 5a9bc844a697ea..df4ce936f2df8d 100644 --- a/.github/workflows/kernel-x86_64-release.config +++ b/.github/workflows/kernel-x86_64-release.config @@ -1391,6 +1391,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/qemu-init.sh b/.github/workflows/qemu-init.sh index 078de0887c6dba..d5e100c6ff188e 100755 --- a/.github/workflows/qemu-init.sh +++ b/.github/workflows/qemu-init.sh @@ -1,5 +1,16 @@ #!/bin/sh +busybox insmod rust_seq_file.ko +busybox mkdir proc +busybox mount -t proc proc /proc +export RUST_SEQ_MINOR=$(busybox cat /proc/misc | busybox grep rust_seq_file | busybox cut -d ' ' -f 1) +busybox mknod /dev/rust_seq_file0 c 10 $RUST_SEQ_MINOR +busybox cat /dev/rust_seq_file0 +busybox cat /dev/rust_seq_file0 +busybox cat /proc/rust_seq_file +busybox rm /dev/rust_seq_file0 +busybox rmmod rust_seq_file.ko + busybox insmod rust_minimal.ko busybox rmmod rust_minimal.ko diff --git a/.github/workflows/qemu-initramfs.desc b/.github/workflows/qemu-initramfs.desc index 5fdcff849dc004..99a828e045d5db 100644 --- a/.github/workflows/qemu-initramfs.desc +++ b/.github/workflows/qemu-initramfs.desc @@ -14,6 +14,7 @@ file /rust_miscdev.ko samples/rust/rust_miscdev.ko 0755 file /rust_stack_probing.ko samples/rust/rust_stack_probing.ko 0755 0 0 file /rust_semaphore.ko samples/rust/rust_semaphore.ko 0755 0 0 file /rust_semaphore_c.ko samples/rust/rust_semaphore_c.ko 0755 0 0 +file /rust_seq_file.ko samples/rust/rust_seq_file.ko 0755 0 0 file /rust_module_parameters_loadable_default.ko samples/rust/rust_module_parameters_loadable_default.ko 0755 0 0 file /rust_module_parameters_loadable_custom.ko samples/rust/rust_module_parameters_loadable_custom.ko 0755 0 0 diff --git a/rust/kernel/bindings_helper.h b/rust/kernel/bindings_helper.h index 6b08f42cfcceb7..0ba6d393e06afb 100644 --- a/rust/kernel/bindings_helper.h +++ b/rust/kernel/bindings_helper.h @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index c9ffeaae18a00f..c6788279766343 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -52,6 +52,7 @@ pub mod module_param; pub mod prelude; pub mod print; pub mod random; +pub mod seq_file; mod static_assert; pub mod sync; diff --git a/rust/kernel/seq_file.rs b/rust/kernel/seq_file.rs new file mode 100644 index 00000000000000..c09e898f4290a4 --- /dev/null +++ b/rust/kernel/seq_file.rs @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Trait for defining `seq_file`s under `/proc`. +//! +//! This module allows Rust devices to implement [`struct seq_operations`] and +//! and create a file under `/proc` based on that implementation. +//! +//! C header: [`include/linux/seq_file.h`](../../../include/linux/seq_file.h) +//! C header: [`include/linux/proc_fs.h`](../../../include/linux/proc_fs.h) +//! +//! Reference: + +use alloc::{boxed::Box, string::ToString}; +use core::{ + fmt::Display, + iter::{Iterator, Peekable}, + marker::{PhantomData, PhantomPinned}, + mem, + ops::Deref, + pin::Pin, + ptr, +}; + +use crate::{bindings, c_types, file_operations::PointerWrapper, CStr, KernelResult}; + +/// Rust equivalent of the [`seq_operations`] interface on the C side. +/// +/// # Example +/// +/// TODO: example +/// +/// [`seq_operations`]: ../../../include/linux/seq_file.h +pub trait SeqOperations { + /// Type produced on each time the iterator is incremented. + type Item: Display; + + /// Type created when the seq file is opened and iterated through to + /// produce the contents of the file. + type Iterator: Iterator; + + /// Container for the [`Self::Iterator`]. + type Wrapper: PointerWrapper> + AsMut>; + + /// Called once each time the `seq_file` is opened. + fn start(arg: &Self) -> Option; +} + +extern "C" fn stop_callback( + _m: *mut bindings::seq_file, + v: *mut c_types::c_void, +) { + if !v.is_null() { + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer + // or pointer generated by `T::Wrapper::into_pointer`. + let _iterator = unsafe { T::Wrapper::from_pointer(v as *const Peekable) }; + } +} + +extern "C" fn next_callback( + _m: *mut bindings::seq_file, + v: *mut c_types::c_void, + pos: *mut bindings::loff_t, +) -> *mut c_types::c_void { + if !v.is_null() { + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer + // or pointer generated by `T::Wrapper::into_pointer`. + // + // The caller guarantees tha `pos` is a valid pointer to an + // `loff_t` and expects this functionto mutate the value. + let mut iterator = unsafe { + *pos += 1; + T::Wrapper::from_pointer(v as *const Peekable) + }; + match iterator.as_mut().next() { + Some(_seen) => iterator.into_pointer() as *mut c_types::c_void, + None => ptr::null_mut(), + } + } else { + ptr::null_mut() + } +} + +extern "C" fn show_callback( + m: *mut bindings::seq_file, + v: *mut c_types::c_void, +) -> c_types::c_int { + if !v.is_null() { + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer + // or pointer generated by `T::Wrapper::into_pointer`. + // + // The caller guarantees tha `pos` is a valid pointer to an + // `loff_t` and expects this functionto mutate the value. + let mut iterator = unsafe { T::Wrapper::from_pointer(v as *const Peekable) }; + let s = match iterator.as_mut().peek() { + // TODO: Replace with fallible `to_string` when available. + Some(item) => item.to_string() + "\0", + None => "\0".to_string(), + }; + // SAFETY: Calling a C function. `s` is guaranteed to be null terminated + // because we explicitly constructed it just above. + unsafe { + bindings::seq_puts(m, s.as_ptr() as *const u8 as *const c_types::c_char); + } + mem::forget(iterator); + } + 0 +} + +extern "C" fn start_callback( + m: *mut bindings::seq_file, + pos: *mut bindings::loff_t, +) -> *mut c_types::c_void { + // SAFETY: This function will be called by opening a proc file generated + // by a call to `proc_create::`, so `m` is contained in a `SeqFile`. + let arg = unsafe { &*SeqFile::::convert(m, pos) }; + // SAFETY: The caller guarantees that `pos` points to a valid `loff_t`. + let pos = unsafe { *pos }; + match T::start(arg) { + Some(mut wrapper) => { + for _ in 0..pos { + wrapper.as_mut().next(); + } + wrapper.into_pointer() as *mut c_types::c_void + } + None => ptr::null_mut(), + } +} + +struct SeqFileOperationsVTable(PhantomData); + +impl SeqFileOperationsVTable { + const VTABLE: bindings::seq_operations = bindings::seq_operations { + start: Some(start_callback::), + stop: Some(stop_callback::), + next: Some(next_callback::), + show: Some(show_callback::), + }; +} + +/// A `seq_file` referencing data of type `T`. +pub struct SeqFile { + seq_ops: bindings::seq_operations, + _pin: PhantomPinned, + context: T, +} + +impl SeqFile { + fn new(seq_ops: bindings::seq_operations, context: T) -> Self { + SeqFile { + seq_ops, + _pin: PhantomPinned, + context, + } + } + + fn new_pinned(seq_ops: bindings::seq_operations, context: T) -> KernelResult>> { + Ok(Pin::from(Box::try_new(Self::new(seq_ops, context))?)) + } + + /// Retrieve the [`SeqFile::context`] associated with a [`bindings::seq_file`]. + /// + /// # Safety + /// + /// `m` must have been created from a proc file generated by calling + /// `proc_create::`. + unsafe fn convert(m: *mut bindings::seq_file, _pos: *mut bindings::loff_t) -> *const T { + let reg = crate::container_of!((*m).op, Self, seq_ops); + &(*reg).context + } +} + +/// Create a `/proc` file. +/// +/// The returned value must not be dropped until the module is unloaded. +pub fn proc_create( + name: CStr<'static>, + context: T, +) -> KernelResult>>> { + let reg = SeqFile::new_pinned(SeqFileOperationsVTable::::VTABLE, context)?; + // SAFETY: Calling a C function. `name` is guaranteed to be null terminated + // because of the type `CStr`. + unsafe { + let _dir_entry = bindings::proc_create_seq_private( + name.deref().as_ptr() as *const u8 as *const c_types::c_char, + 0, + ptr::null_mut(), + ®.seq_ops, + 0, + ptr::null_mut(), + ); + Ok(reg) + } +} diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 8eb5d5ebdf000a..0b29f2048a5239 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -100,4 +100,14 @@ config SAMPLE_RUST_SEMAPHORE_C If unsure, say N. +config SAMPLE_RUST_SEQ_FILE + tristate "Seq file" + help + This option builds the Rust seq_file sample. + + To compile this as a module, choose M here: + the module will be called rust_seq_file. + + If unsure, say N. + endif # SAMPLES_RUST diff --git a/samples/rust/Makefile b/samples/rust/Makefile index c53a71999d8f6e..e233979c47f2dd 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_SAMPLE_RUST_MISCDEV) += rust_miscdev.o obj-$(CONFIG_SAMPLE_RUST_STACK_PROBING) += rust_stack_probing.o obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE) += rust_semaphore.o obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE_C) += rust_semaphore_c.o +obj-$(CONFIG_SAMPLE_RUST_SEQ_FILE) += rust_seq_file.o diff --git a/samples/rust/rust_seq_file.rs b/samples/rust/rust_seq_file.rs new file mode 100644 index 00000000000000..1e1c408a7271f5 --- /dev/null +++ b/samples/rust/rust_seq_file.rs @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Example of using a [`seq_file`] in Rust. +//! +//! C header: [`include/linux/seq_file.h`](../../../include/linux/seq_file.h) +//! C header: [`include/linux/proc_fs.h`](../../../include/linux/proc_fs.h) +//! +//! Reference: + +#![no_std] +#![feature(allocator_api, global_asm, try_reserve)] + +use alloc::{boxed::Box, sync::Arc}; +use core::{ + convert::TryInto, + fmt::Write, + iter::{repeat, Peekable, Repeat, Take}, + pin::Pin, +}; +use kernel::{ + cstr, + file_operations::{File, FileOpener, FileOperations}, + miscdev, mutex_init, seq_file, + sync::Mutex, + user_ptr::UserSlicePtrWriter, +}; + +use kernel::prelude::*; + +module! { + type: RustSeqFileDev, + name: b"rust_seq_file", + author: b"Adam Bratschi-Kaye", + description: b"Rust sample using a seq_file", + license: b"GPL v2", + params: { + }, +} + +#[derive(Clone)] +struct SharedState(Arc>); + +impl SharedState { + fn try_new() -> KernelResult { + let state = Arc::try_new( + // SAFETY: `mutex_init!` is called below. + unsafe { Mutex::new(0) }, + )?; + // SAFETY: Mutex is pinned behind `Arc`. + let pin_state = unsafe { Pin::new_unchecked(state.as_ref()) }; + mutex_init!(pin_state, "SharedState::0"); + Ok(SharedState(state)) + } +} + +impl seq_file::SeqOperations for SharedState { + type Item = String; + type Iterator = Take>; + type Wrapper = Box>>>; + + fn start(arg: &SharedState) -> Option { + let count = arg.0.lock(); + let mut message = String::new(); + + let template = "rust_seq_file: device opened this many times: "; + message.try_reserve_exact(template.len() + 4).ok()?; + // NO PANIC: We reserved space for `template` above. + message.push_str(template); + if *count < 1000 { + // NO PANIC: There are 4 characters remaining in the string which + // leaves space for a 3 digit number and the newline. + write!(&mut message, "{}\n", *count).ok()?; + } + + Box::try_new(repeat(message).take((*count).try_into().ok()?).peekable()).ok() + } +} + +impl FileOpener for SharedState { + fn open(ctx: &SharedState) -> KernelResult { + pr_info!("rust seq_file was opened!\n"); + Ok(Box::try_new(ctx.clone())?) + } +} + +impl FileOperations for SharedState { + type Wrapper = Box; + + kernel::declare_file_operations!(read); + + fn read(&self, _: &File, data: &mut UserSlicePtrWriter, offset: u64) -> KernelResult { + let message = b"incremented read count\n"; + if offset != 0 { + return Ok(0); + } + + { + let mut count = self.0.lock(); + *count += 1; + } + + data.write_slice(message)?; + Ok(message.len()) + } +} + +struct RustSeqFileDev { + _seq: Pin>>, + _dev: Pin>>, +} + +impl KernelModule for RustSeqFileDev { + fn init() -> KernelResult { + pr_info!("Rust seq_file sample (init)\n"); + + let state = SharedState::try_new()?; + + let seq_reg = + kernel::seq_file::proc_create::(cstr!("rust_seq_file"), state.clone())?; + + let dev_reg = + miscdev::Registration::new_pinned::(cstr!("rust_seq_file"), None, state)?; + + let dev = RustSeqFileDev { + _seq: seq_reg, + _dev: dev_reg, + }; + + Ok(dev) + } +} + +impl Drop for RustSeqFileDev { + fn drop(&mut self) { + pr_info!("Rust seq_file sample (exit)\n"); + } +}