From 530f732ce8dd1958f32384c6518681831f692cf8 Mon Sep 17 00:00:00 2001 From: Tae-il Lim Date: Sat, 3 Sep 2016 21:50:50 +0900 Subject: [PATCH 1/6] Implement CFFileDescriptor --- core-foundation-sys/src/filedescriptor.rs | 48 +++++++++++++++ core-foundation-sys/src/lib.rs | 1 + core-foundation/src/filedescriptor.rs | 71 +++++++++++++++++++++++ core-foundation/src/lib.rs | 1 + 4 files changed, 121 insertions(+) create mode 100644 core-foundation-sys/src/filedescriptor.rs create mode 100644 core-foundation/src/filedescriptor.rs diff --git a/core-foundation-sys/src/filedescriptor.rs b/core-foundation-sys/src/filedescriptor.rs new file mode 100644 index 0000000..8ebb5c1 --- /dev/null +++ b/core-foundation-sys/src/filedescriptor.rs @@ -0,0 +1,48 @@ +use libc::{c_void, c_int}; +use base::{Boolean, CFIndex, CFTypeID, CFOptionFlags, CFAllocatorRef}; +use string::CFStringRef; +use runloop::CFRunLoopSourceRef; + +pub type CFFileDescriptorNativeDescriptor = c_int; + +#[repr(C)] +pub struct __CFFileDescriptor(c_void); + +pub type CFFileDescriptorRef = *const __CFFileDescriptor; + +/* Callback Reason Types */ +pub const kCFFileDescriptorReadCallBack: CFOptionFlags = 1 << 0; +pub const kCFFileDescriptorWriteCallBack: CFOptionFlags = 1 << 1; + +pub type CFFileDescriptorCallBack = extern "C" fn (f: CFFileDescriptorRef, callBackTypes: CFOptionFlags, info: *mut c_void); + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct CFFileDescriptorContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: Option *const c_void>, + pub release: Option, + pub copyDescription: Option CFStringRef>, +} + +extern { + /* + * CFFileDescriptor.h + */ + pub fn CFFileDescriptorGetTypeID() -> CFTypeID; + + pub fn CFFileDescriptorCreate(allocator: CFAllocatorRef, fd: CFFileDescriptorNativeDescriptor, closeOnInvalidate: Boolean, callout: CFFileDescriptorCallBack, context: *const CFFileDescriptorContext) -> CFFileDescriptorRef; + + pub fn CFFileDescriptorGetNativeDescriptor(f: CFFileDescriptorRef) -> CFFileDescriptorNativeDescriptor; + + pub fn CFFileDescriptorGetContext(f: CFFileDescriptorRef, context: *mut CFFileDescriptorContext); + + pub fn CFFileDescriptorEnableCallBacks(f: CFFileDescriptorRef, callBackTypes: CFOptionFlags); + pub fn CFFileDescriptorDisableCallBacks(f: CFFileDescriptorRef, callBackTypes: CFOptionFlags); + + pub fn CFFileDescriptorInvalidate(f: CFFileDescriptorRef); + pub fn CFFileDescriptorIsValid(f: CFFileDescriptorRef) -> Boolean; + + pub fn CFFileDescriptorCreateRunLoopSource(allocator: CFAllocatorRef, f: CFFileDescriptorRef, order: CFIndex) -> CFRunLoopSourceRef; +} diff --git a/core-foundation-sys/src/lib.rs b/core-foundation-sys/src/lib.rs index d2744e2..330de30 100644 --- a/core-foundation-sys/src/lib.rs +++ b/core-foundation-sys/src/lib.rs @@ -19,6 +19,7 @@ pub mod data; pub mod date; pub mod dictionary; pub mod error; +pub mod filedescriptor; pub mod messageport; pub mod number; pub mod propertylist; diff --git a/core-foundation/src/filedescriptor.rs b/core-foundation/src/filedescriptor.rs new file mode 100644 index 0000000..d42f1c3 --- /dev/null +++ b/core-foundation/src/filedescriptor.rs @@ -0,0 +1,71 @@ +pub use core_foundation_sys::filedescriptor::*; + +use core_foundation_sys::base::{Boolean, CFIndex, CFRelease}; +use core_foundation_sys::base::{kCFAllocatorDefault, CFOptionFlags}; + +use base::{TCFType}; + +use std::os::unix::io::{AsRawFd, RawFd}; + +pub struct CFFileDescriptor(CFFileDescriptorRef); + +impl Drop for CFFileDescriptor { + fn drop(&mut self) { + unsafe { + CFRelease(self.as_CFTypeRef()) + } + } +} + +impl_TCFType!(CFFileDescriptor, CFFileDescriptorRef, CFFileDescriptorGetTypeID); + +impl CFFileDescriptor { + pub unsafe fn new(fd: RawFd, + closeOnInvalidate: bool, + callout: CFFileDescriptorCallBack, + context: *const CFFileDescriptorContext) -> CFFileDescriptor { + let fd_ref = CFFileDescriptorCreate(kCFAllocatorDefault, + fd, + closeOnInvalidate as Boolean, + callout, + context); + TCFType::wrap_under_create_rule(fd_ref) + } + + pub fn enable_callbacks(&self, callback_types: CFOptionFlags) { + unsafe { + CFFileDescriptorEnableCallBacks(self.0, callback_types) + } + } + + pub fn disable_callbacks(&self, callback_types: CFOptionFlags) { + unsafe { + CFFileDescriptorDisableCallBacks(self.0, callback_types) + } + } + + pub fn valid(&self) -> bool { + unsafe { + CFFileDescriptorIsValid(self.0) != 0 + } + } + + pub fn invalidate(&self) { + unsafe { + CFFileDescriptorInvalidate(self.0) + } + } +} + +impl AsRawFd for CFFileDescriptor { + fn as_raw_fd(&self) -> RawFd { + unsafe { + CFFileDescriptorGetNativeDescriptor(self.0) + } + } +} + +#[cfg(test)] +mod test { + use super::*; +} diff --git a/core-foundation/src/lib.rs b/core-foundation/src/lib.rs index 775cd23..fae462b 100644 --- a/core-foundation/src/lib.rs +++ b/core-foundation/src/lib.rs @@ -177,6 +177,7 @@ pub mod data; pub mod date; pub mod dictionary; pub mod error; +pub mod filedescriptor; pub mod number; pub mod set; pub mod string; From 2a19e3f79eb063bfffcf8f01509d1486b761e0de Mon Sep 17 00:00:00 2001 From: Tae-il Lim Date: Mon, 15 Jan 2018 17:20:07 +0900 Subject: [PATCH 2/6] Safer handling of CFFileDescriptorContext --- core-foundation/src/filedescriptor.rs | 36 +++++++++++++++++++-------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/core-foundation/src/filedescriptor.rs b/core-foundation/src/filedescriptor.rs index d42f1c3..aaf9586 100644 --- a/core-foundation/src/filedescriptor.rs +++ b/core-foundation/src/filedescriptor.rs @@ -5,7 +5,9 @@ use core_foundation_sys::base::{kCFAllocatorDefault, CFOptionFlags}; use base::{TCFType}; +use std::mem; use std::os::unix::io::{AsRawFd, RawFd}; +use std::ptr; pub struct CFFileDescriptor(CFFileDescriptorRef); @@ -20,16 +22,30 @@ impl Drop for CFFileDescriptor { impl_TCFType!(CFFileDescriptor, CFFileDescriptorRef, CFFileDescriptorGetTypeID); impl CFFileDescriptor { - pub unsafe fn new(fd: RawFd, - closeOnInvalidate: bool, - callout: CFFileDescriptorCallBack, - context: *const CFFileDescriptorContext) -> CFFileDescriptor { - let fd_ref = CFFileDescriptorCreate(kCFAllocatorDefault, - fd, - closeOnInvalidate as Boolean, - callout, - context); - TCFType::wrap_under_create_rule(fd_ref) + pub fn new(fd: RawFd, + closeOnInvalidate: bool, + callout: CFFileDescriptorCallBack, + context: Option<&CFFileDescriptorContext>) -> CFFileDescriptor { + unsafe { + let fd_ref = CFFileDescriptorCreate(kCFAllocatorDefault, + fd, + closeOnInvalidate as Boolean, + callout, + if let Some(context) = context { + context + } else { + ptr::null() + }); + TCFType::wrap_under_create_rule(fd_ref) + } + } + + pub fn context(&self) -> CFFileDescriptorContext { + unsafe { + let mut context: CFFileDescriptorContext = mem::uninitialized(); + CFFileDescriptorGetContext(self.0, &mut context); + context + } } pub fn enable_callbacks(&self, callback_types: CFOptionFlags) { From c5d0134e965aead0c6227736f84eb449ce13bf38 Mon Sep 17 00:00:00 2001 From: Tae-il Lim Date: Mon, 15 Jan 2018 17:21:37 +0900 Subject: [PATCH 3/6] Add method to create CFRunLoopSource from CFFileDescriptor --- core-foundation/src/filedescriptor.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core-foundation/src/filedescriptor.rs b/core-foundation/src/filedescriptor.rs index aaf9586..60413c6 100644 --- a/core-foundation/src/filedescriptor.rs +++ b/core-foundation/src/filedescriptor.rs @@ -81,6 +81,17 @@ impl AsRawFd for CFFileDescriptor { } } +use runloop::{CFRunLoopSource}; + +impl CFRunLoopSource { + pub fn from_file_descriptor(fd: &CFFileDescriptor, order: CFIndex) -> CFRunLoopSource { + unsafe { + let source_ref = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fd.0, order); + TCFType::wrap_under_create_rule(source_ref) + } + } +} + #[cfg(test)] mod test { use super::*; From d6fe0651fe904a295d3dd4978b0d7bdb8e71d69b Mon Sep 17 00:00:00 2001 From: Tae-il Lim Date: Mon, 15 Jan 2018 17:22:13 +0900 Subject: [PATCH 4/6] Add filedescriptor test --- core-foundation/src/filedescriptor.rs | 93 +++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/core-foundation/src/filedescriptor.rs b/core-foundation/src/filedescriptor.rs index 60413c6..0e60253 100644 --- a/core-foundation/src/filedescriptor.rs +++ b/core-foundation/src/filedescriptor.rs @@ -94,5 +94,98 @@ impl CFRunLoopSource { #[cfg(test)] mod test { + extern crate libc; + use super::*; + use std::ffi::CString; + use libc::{c_void, O_RDWR}; + use core_foundation_sys::base::{CFOptionFlags}; + use core_foundation_sys::runloop::{kCFRunLoopDefaultMode}; + use runloop::{CFRunLoop}; + + #[test] + fn test_consumed() { + let path = CString::new("/dev/null").unwrap(); + let raw_fd = unsafe { libc::open(path.as_ptr(), O_RDWR, 0) }; + let cf_fd = CFFileDescriptor::new(raw_fd, true, never_callback, None); + + assert!(cf_fd.valid()); + cf_fd.invalidate(); + + // close() should fail + assert_eq!(unsafe { libc::close(raw_fd) }, -1); + } + + #[test] + fn test_unconsumed() { + let path = CString::new("/dev/null").unwrap(); + let raw_fd = unsafe { libc::open(path.as_ptr(), O_RDWR, 0) }; + let cf_fd = CFFileDescriptor::new(raw_fd, false, never_callback, None); + + assert!(cf_fd.valid()); + cf_fd.invalidate(); + + // close() should succeed + assert_eq!(unsafe { libc::close(raw_fd) }, 0); + } + + extern "C" fn never_callback(_f: CFFileDescriptorRef, + _callback_types: CFOptionFlags, + _info_ptr: *mut c_void) { + // should never be called + assert!(false); + } + + struct TestInfo { + value: CFOptionFlags + } + + #[test] + fn test_callback() { + let mut info = TestInfo { value: 0 }; + let context = CFFileDescriptorContext { + version: 0, + info: &mut info as *mut _ as *mut c_void, + retain: None, + release: None, + copyDescription: None + }; + + let path = CString::new("/dev/null").unwrap(); + let raw_fd = unsafe { libc::open(path.as_ptr(), O_RDWR, 0) }; + let cf_fd = CFFileDescriptor::new(raw_fd, true, callback, Some(&context)); + + assert!(cf_fd.valid()); + + let runloop = CFRunLoop::get_current(); + let source = CFRunLoopSource::from_file_descriptor(&cf_fd, 0); + unsafe { + runloop.add_source(&source, kCFRunLoopDefaultMode); + } + + info.value = 0; + cf_fd.enable_callbacks(kCFFileDescriptorReadCallBack); + CFRunLoop::run_current(); + assert_eq!(info.value, kCFFileDescriptorReadCallBack); + + info.value = 0; + cf_fd.enable_callbacks(kCFFileDescriptorWriteCallBack); + CFRunLoop::run_current(); + assert_eq!(info.value, kCFFileDescriptorWriteCallBack); + + info.value = 0; + cf_fd.disable_callbacks(kCFFileDescriptorReadCallBack|kCFFileDescriptorWriteCallBack); + + cf_fd.invalidate(); + } + + extern "C" fn callback(_f: CFFileDescriptorRef, callback_types: CFOptionFlags, info_ptr: *mut c_void) { + assert!(info_ptr != ptr::null_mut()); + + let info: *mut TestInfo = info_ptr as *mut TestInfo; + + unsafe { (*info).value = callback_types }; + + CFRunLoop::get_current().stop(); + } } From 5285fe083610ef8c2706ac76918c315cdfb04020 Mon Sep 17 00:00:00 2001 From: Tae-il Lim Date: Thu, 18 Jan 2018 11:32:00 +0900 Subject: [PATCH 5/6] Change create methods to return Option to handle NULL return value --- core-foundation/src/filedescriptor.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/core-foundation/src/filedescriptor.rs b/core-foundation/src/filedescriptor.rs index 0e60253..fbc60eb 100644 --- a/core-foundation/src/filedescriptor.rs +++ b/core-foundation/src/filedescriptor.rs @@ -25,7 +25,7 @@ impl CFFileDescriptor { pub fn new(fd: RawFd, closeOnInvalidate: bool, callout: CFFileDescriptorCallBack, - context: Option<&CFFileDescriptorContext>) -> CFFileDescriptor { + context: Option<&CFFileDescriptorContext>) -> Option { unsafe { let fd_ref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, @@ -36,7 +36,11 @@ impl CFFileDescriptor { } else { ptr::null() }); - TCFType::wrap_under_create_rule(fd_ref) + if fd_ref.is_null() { + None + } else { + Some(TCFType::wrap_under_create_rule(fd_ref)) + } } } @@ -84,10 +88,14 @@ impl AsRawFd for CFFileDescriptor { use runloop::{CFRunLoopSource}; impl CFRunLoopSource { - pub fn from_file_descriptor(fd: &CFFileDescriptor, order: CFIndex) -> CFRunLoopSource { + pub fn from_file_descriptor(fd: &CFFileDescriptor, order: CFIndex) -> Option { unsafe { let source_ref = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fd.0, order); - TCFType::wrap_under_create_rule(source_ref) + if source_ref.is_null() { + None + } else { + Some(TCFType::wrap_under_create_rule(source_ref)) + } } } } @@ -108,6 +116,8 @@ mod test { let path = CString::new("/dev/null").unwrap(); let raw_fd = unsafe { libc::open(path.as_ptr(), O_RDWR, 0) }; let cf_fd = CFFileDescriptor::new(raw_fd, true, never_callback, None); + assert!(cf_fd.is_some()); + let cf_fd = cf_fd.unwrap(); assert!(cf_fd.valid()); cf_fd.invalidate(); @@ -121,6 +131,8 @@ mod test { let path = CString::new("/dev/null").unwrap(); let raw_fd = unsafe { libc::open(path.as_ptr(), O_RDWR, 0) }; let cf_fd = CFFileDescriptor::new(raw_fd, false, never_callback, None); + assert!(cf_fd.is_some()); + let cf_fd = cf_fd.unwrap(); assert!(cf_fd.valid()); cf_fd.invalidate(); @@ -154,13 +166,16 @@ mod test { let path = CString::new("/dev/null").unwrap(); let raw_fd = unsafe { libc::open(path.as_ptr(), O_RDWR, 0) }; let cf_fd = CFFileDescriptor::new(raw_fd, true, callback, Some(&context)); + assert!(cf_fd.is_some()); + let cf_fd = cf_fd.unwrap(); assert!(cf_fd.valid()); let runloop = CFRunLoop::get_current(); let source = CFRunLoopSource::from_file_descriptor(&cf_fd, 0); + assert!(source.is_some()); unsafe { - runloop.add_source(&source, kCFRunLoopDefaultMode); + runloop.add_source(&source.unwrap(), kCFRunLoopDefaultMode); } info.value = 0; From 9c362576605e425c1b3a2bde41798c1f4f22ef09 Mon Sep 17 00:00:00 2001 From: Tae-il Lim Date: Thu, 18 Jan 2018 11:34:04 +0900 Subject: [PATCH 6/6] Improve tests and code readability --- core-foundation/src/filedescriptor.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/core-foundation/src/filedescriptor.rs b/core-foundation/src/filedescriptor.rs index fbc60eb..8794db2 100644 --- a/core-foundation/src/filedescriptor.rs +++ b/core-foundation/src/filedescriptor.rs @@ -26,16 +26,13 @@ impl CFFileDescriptor { closeOnInvalidate: bool, callout: CFFileDescriptorCallBack, context: Option<&CFFileDescriptorContext>) -> Option { + let context = context.map_or(ptr::null(), |c| c as *const _); unsafe { let fd_ref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, closeOnInvalidate as Boolean, callout, - if let Some(context) = context { - context - } else { - ptr::null() - }); + context); if fd_ref.is_null() { None } else { @@ -121,6 +118,7 @@ mod test { assert!(cf_fd.valid()); cf_fd.invalidate(); + assert!(!cf_fd.valid()); // close() should fail assert_eq!(unsafe { libc::close(raw_fd) }, -1); @@ -136,6 +134,7 @@ mod test { assert!(cf_fd.valid()); cf_fd.invalidate(); + assert!(!cf_fd.valid()); // close() should succeed assert_eq!(unsafe { libc::close(raw_fd) }, 0); @@ -144,8 +143,7 @@ mod test { extern "C" fn never_callback(_f: CFFileDescriptorRef, _callback_types: CFOptionFlags, _info_ptr: *mut c_void) { - // should never be called - assert!(false); + unreachable!(); } struct TestInfo { @@ -189,13 +187,14 @@ mod test { assert_eq!(info.value, kCFFileDescriptorWriteCallBack); info.value = 0; - cf_fd.disable_callbacks(kCFFileDescriptorReadCallBack|kCFFileDescriptorWriteCallBack); + cf_fd.disable_callbacks(kCFFileDescriptorReadCallBack | kCFFileDescriptorWriteCallBack); cf_fd.invalidate(); + assert!(!cf_fd.valid()); } extern "C" fn callback(_f: CFFileDescriptorRef, callback_types: CFOptionFlags, info_ptr: *mut c_void) { - assert!(info_ptr != ptr::null_mut()); + assert!(!info_ptr.is_null()); let info: *mut TestInfo = info_ptr as *mut TestInfo;