summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/sys/ioctl.rs229
-rw-r--r--test/sys/mod.rs1
-rw-r--r--test/sys/test_ioctl.rs31
3 files changed, 229 insertions, 32 deletions
diff --git a/src/sys/ioctl.rs b/src/sys/ioctl.rs
index 18b8b1ab..0e56e96b 100644
--- a/src/sys/ioctl.rs
+++ b/src/sys/ioctl.rs
@@ -1,45 +1,210 @@
-use libc;
+///! Provide helpers for making ioctl system calls
+///!
+///! # Overview of IOCTLs
+///!
+///! The `ioctl` system call is a widely support system
+///! call on *nix systems providing access to functions
+///! and data that do not fit nicely into the standard
+///! read and write operations on a file itself. It is
+///! common to see ioctls used for the following purposes:
+///!
+///! * Provide read/write access to out-of-band data related
+///! to a device such as configuration (for instance, setting
+///! serial port options)
+///! * Provide a mechanism for performing full-duplex data
+///! transfers (for instance, xfer on SPI devices).
+///! * Provide access to control functions on a device (for example,
+///! on Linux you can send commands like pause, resume, and eject
+///! to the CDROM device.
+///! * Do whatever else the device driver creator thought made most sense.
+///!
+///! Ioctls are synchronous system calls and are similar to read and
+///! write calls in that regard.
+///!
+///! The prototype for the ioctl system call in libc is as follows:
+///!
+///! ```c
+///! int ioctl(int fd, unsigned long request, ...);
+///! ```
+///!
+///! Typically, an ioctl takes 3 parameters as arguments:
+///! 1. An open file descriptor, `fd`.
+///! 2. An device-dependennt request code or operation. This request
+///! code is referred to as `op` in this module.
+///! 3. Either a pointer to a location in memory or an integer. This
+///! number of pointer may either be used by the kernel or written
+///! to by the kernel depending on how the operation is documented
+///! to work.
+///!
+///! The `op` request code is essentially an arbitrary integer having
+///! a device-driver specific meaning. Over time, it proved difficult
+///! for various driver implementors to use this field sanely, so a
+///! convention with macros was introduced to the Linux Kernel that
+///! is used by most newer drivers. See
+///! https://github.com/torvalds/linux/blob/master/Documentation/ioctl/ioctl-number.txt
+///! for additional details. The macros exposed by the kernel for
+///! consumers are implemented in this module and may be used to
+///! instead of calls like `_IOC`, `_IO`, `_IOR`, and `_IOW`.
+///!
+///! # Interface Overview
+///!
+///! This ioctl module seeks to tame the ioctl beast by providing
+///! a set of safer (although not safe) functions
+///! implementing the most common ioctl access patterns.
+///!
+///! The most common access patterns for ioctls are as follows:
+///! 1. `read`: A pointer is provided to the kernel which is populated
+///! with a value containing the "result" of the operation. The
+///! result may be an integer or structure. The kernel may also
+///! read values from the provided pointer (usually a structure).
+///! 2. `write`: A pointer is provided to the kernel containing values
+///! that the kernel will read in order to perform the operation.
+///! 3. `execute`: The operation is passed to the kernel but no
+///! additional pointer is passed. The operation is enough
+///! and it either succeeds or results in an error.
+///!
+///! Where appropriate, versions of these interface function are provided
+///! taking either refernces or pointers. The pointer versions are
+///! necessary for cases (notably slices) where a reference cannot
+///! be generically cast to a pointer.
+
+use libc::funcs::bsd44::ioctl as libc_ioctl;
+use std::mem;
use fcntl::Fd;
-use {Result, from_ffi};
+use {Error, Result, errno};
-pub use self::ffi::Winsize;
-pub use self::IoctlArg::*;
+pub type ioctl_op_t = u32;
-mod ffi {
- use libc::c_ushort;
+// low-level ioctl functions and definitions matching the
+// macros provided in ioctl.h from the kernel
+const IOC_NRBITS: u32 = 8;
+const IOC_TYPEBITS: u32 = 8;
+const IOC_SIZEBITS: u32 = 14;
+// const IOC_DIRBITS: u32 = 2;
- #[derive(Clone, Copy, Debug)]
- pub struct Winsize {
- pub ws_row: c_ushort,
- pub ws_col: c_ushort,
- pub ws_xpixel: c_ushort,
- pub ws_ypixel: c_ushort,
- }
+const IOC_NRSHIFT: u32 = 0;
+const IOC_TYPESHIFT: u32 = IOC_NRSHIFT + IOC_NRBITS;
+const IOC_SIZESHIFT: u32 = IOC_TYPESHIFT + IOC_TYPEBITS;
+const IOC_DIRSHIFT: u32 = IOC_SIZESHIFT + IOC_SIZEBITS;
- #[cfg(target_os = "macos")]
- pub mod os {
- use libc::c_ulong;
- pub const TIOCGWINSZ: c_ulong = 0x40087468;
+/// Flags indicating the direction of the ioctl operation
+/// for ioctls using modern operation conventions
+bitflags! {
+ flags IoctlDirFlags: u8 {
+ /// Indicates that the ioctl data pointer is not used
+ const IOC_NONE = 0x00,
+ /// Indicates that the ioctl data pointer contains data that
+ /// will be consumed by the operating system
+ const IOC_WRITE = 0x01,
+ /// Indicates tha the ioctl data pointer contains data that
+ /// will be populated by the operating system to be consumed
+ /// by userspace
+ const IOC_READ = 0x02,
}
+}
- #[cfg(any(target_os = "linux",
- all(target_os = "android", not(target_arch = "mips"))))]
- pub mod os {
- use libc::c_int;
- pub const TIOCGWINSZ: c_int = 0x5413;
- }
+/// Build an ioctl op with the provide parameters. This is a helper
+/// function for IOCTLs in the Linux kernel using the newer conventions
+/// for IOCTLs operations. Many ioctls do not use this newer convention
+/// and the constants for those should just be used as-is.
+///
+/// This provides the same functionality as the Linux `_IOC` macro.
+pub fn op(dir: IoctlDirFlags, ioctl_type: u8, nr: u8, size: usize) -> ioctl_op_t {
+ // actual number will always fit in 32 bits, but ioctl() expects
+ // an unsigned long for the op
+ let size_to_use: u32 = if size < (1 << IOC_SIZEBITS) { size as u32 } else { 0 };
+ (((dir.bits as u32) << IOC_DIRSHIFT) |
+ ((ioctl_type as u32) << IOC_TYPESHIFT) |
+ ((nr as u32) << IOC_NRSHIFT) |
+ ((size_to_use) << IOC_SIZESHIFT)) as ioctl_op_t
+}
+
+/// Build an op indicating that the data pointer is not used.
+/// That is, the command itself is sufficient.
+///
+/// This provides the same functionality the Linux `_IO` macro.
+pub fn op_none(ioctl_type: u8, nr: u8) -> ioctl_op_t {
+ op(IOC_NONE, ioctl_type, nr, 0)
}
-pub enum IoctlArg<'a> {
- TIOCGWINSZ(&'a mut Winsize)
+/// Build an op indicating that the data pointer will be populated
+/// with data from the kernel
+///
+/// This provides the same functionality as the Linux `_IOR` macro.
+pub fn op_read(ioctl_type: u8, nr: u8, size: usize) -> ioctl_op_t {
+ op(IOC_READ, ioctl_type, nr, size)
}
-pub fn ioctl(fd: Fd, arg: IoctlArg) -> Result<()> {
- match arg {
- TIOCGWINSZ(&mut ref winsize) => {
- from_ffi(unsafe {
- libc::funcs::bsd44::ioctl(fd, ffi::os::TIOCGWINSZ, winsize)
- })
- }
+/// Build an op indicating that the data pointer contains data
+/// to be consumed by the kernel (and not written to).
+///
+/// This provides the same functionality as the Linux `_IOW` macro.
+pub fn op_write(ioctl_type: u8, nr: u8, size: usize) -> ioctl_op_t {
+ op(IOC_WRITE, ioctl_type, nr, size)
+}
+
+/// Build an op indicating that the data pointer both contains
+/// data to be consumed by the kernel and contains fields that
+/// will be populated by the kernel.
+///
+/// This provides the same functionality as the Linux `_IOWR` macro.
+pub fn op_read_write(ioctl_type: u8, nr: u8, size: usize) -> ioctl_op_t {
+ op(IOC_WRITE | IOC_READ, ioctl_type, nr, size)
+}
+
+fn convert_ioctl_res(res: i32) -> Result<i32> {
+ if res < 0 {
+ return Err(Error::Sys(errno::Errno::last()))
}
+ Ok(res) // res may length or similar useful to caller
+}
+
+/// Ioctl call that is expected to return a result
+/// but which does not take any additional arguments on the input side
+///
+/// This function will allocate allocate space for and returned an owned
+/// reference to the result.
+pub unsafe fn read<T>(fd: Fd, op: ioctl_op_t) -> Result<T> {
+ // allocate memory for the result (should get a value from kernel)
+ let mut dst: T = mem::zeroed();
+ let dst_ptr: *mut T = &mut dst;
+ try!(read_into_ptr(fd, op, dst_ptr));
+ Ok(dst)
+}
+
+/// Ioctl where the result from the kernel will be written to the
+/// provided reference
+///
+/// The refernced data may also contain information that will be consumed
+/// by the kernel.
+pub unsafe fn read_into<T>(fd: Fd, op: ioctl_op_t, data: &mut T) -> Result<i32> {
+ read_into_ptr(fd, op, data as *mut T)
+}
+
+/// Ioctl where the result from the kernel will be written to the
+/// provided pointer
+///
+/// The refernced data may also contain information that will be consumed
+/// by the kernel.
+pub unsafe fn read_into_ptr<T>(fd: Fd, op: ioctl_op_t, data_ptr: *mut T) -> Result<i32> {
+ convert_ioctl_res(libc_ioctl(fd, op as i32, data_ptr))
+}
+
+/// Ioctl call that sends a value to the kernel but
+/// does not return anything (pure side effect).
+pub unsafe fn write<T>(fd: Fd, op: ioctl_op_t, data: &T) -> Result<i32> {
+ write_ptr(fd, op, data as *const T)
+}
+
+/// Ioctl call that sends a value to the kernel but
+/// does not return anything (pure side effect).
+pub unsafe fn write_ptr<T>(fd: Fd, op: ioctl_op_t, data: *const T) -> Result<i32> {
+ convert_ioctl_res(libc_ioctl(fd, op as i32, data as *const T))
+}
+
+/// Ioctl call for which no data pointer is provided to the kernel.
+/// That is, the kernel has sufficient information about what to
+/// do based on the op alone.
+pub fn execute(fd: Fd, op: ioctl_op_t) -> Result<i32> {
+ convert_ioctl_res(unsafe { libc_ioctl(fd, op as i32) })
}
diff --git a/test/sys/mod.rs b/test/sys/mod.rs
index 21c5817c..17649e24 100644
--- a/test/sys/mod.rs
+++ b/test/sys/mod.rs
@@ -1,3 +1,4 @@
mod test_socket;
mod test_termios;
mod test_uio;
+mod test_ioctl;
diff --git a/test/sys/test_ioctl.rs b/test/sys/test_ioctl.rs
new file mode 100644
index 00000000..2654bd81
--- /dev/null
+++ b/test/sys/test_ioctl.rs
@@ -0,0 +1,31 @@
+use nix::sys::ioctl::*;
+
+// See C code for source of values for op calculations:
+// https://gist.github.com/posborne/83ea6880770a1aef332e
+
+#[test]
+fn test_op_none() {
+ assert_eq!(op_none('q' as u8, 10), 0x0000710A);
+ assert_eq!(op_none('a' as u8, 255), 0x000061FF);
+}
+
+#[test]
+fn test_op_write() {
+ assert_eq!(op_write('z' as u8, 10, 1), 0x40017A0A);
+ assert_eq!(op_write('z' as u8, 10, 512), 0x42007A0A);
+ assert_eq!(op_write('z' as u8, 10, 1 << 32), 0x40007A0A);
+}
+
+#[test]
+fn test_op_read() {
+ assert_eq!(op_read('z' as u8, 10, 1), 0x80017A0A);
+ assert_eq!(op_read('z' as u8, 10, 512), 0x82007A0A);
+ assert_eq!(op_read('z' as u8, 10, 1 << 32), 0x80007A0A);
+}
+
+#[test]
+fn test_op_read_write() {
+ assert_eq!(op_read_write('z' as u8, 10, 1), 0xC0017A0A);
+ assert_eq!(op_read_write('z' as u8, 10, 512), 0xC2007A0A);
+ assert_eq!(op_read_write('z' as u8, 10, 1 << 32), 0xC0007A0A);
+}