core_foundation/
filedescriptor.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10pub use core_foundation_sys::filedescriptor::*;
11
12use core_foundation_sys::base::{kCFAllocatorDefault, CFOptionFlags};
13use core_foundation_sys::base::{Boolean, CFIndex};
14
15use crate::base::TCFType;
16use crate::runloop::CFRunLoopSource;
17
18use std::mem::MaybeUninit;
19use std::os::unix::io::{AsRawFd, RawFd};
20use std::ptr;
21
22declare_TCFType! {
23    CFFileDescriptor, CFFileDescriptorRef
24}
25impl_TCFType!(
26    CFFileDescriptor,
27    CFFileDescriptorRef,
28    CFFileDescriptorGetTypeID
29);
30
31impl CFFileDescriptor {
32    pub fn new(
33        fd: RawFd,
34        closeOnInvalidate: bool,
35        callout: CFFileDescriptorCallBack,
36        context: Option<&CFFileDescriptorContext>,
37    ) -> Option<CFFileDescriptor> {
38        let context = context.map_or(ptr::null(), |c| c as *const _);
39        unsafe {
40            let fd_ref = CFFileDescriptorCreate(
41                kCFAllocatorDefault,
42                fd,
43                closeOnInvalidate as Boolean,
44                callout,
45                context,
46            );
47            if fd_ref.is_null() {
48                None
49            } else {
50                Some(TCFType::wrap_under_create_rule(fd_ref))
51            }
52        }
53    }
54
55    pub fn context(&self) -> CFFileDescriptorContext {
56        unsafe {
57            let mut context = MaybeUninit::<CFFileDescriptorContext>::uninit();
58            CFFileDescriptorGetContext(self.0, context.as_mut_ptr());
59            context.assume_init()
60        }
61    }
62
63    pub fn enable_callbacks(&self, callback_types: CFOptionFlags) {
64        unsafe { CFFileDescriptorEnableCallBacks(self.0, callback_types) }
65    }
66
67    pub fn disable_callbacks(&self, callback_types: CFOptionFlags) {
68        unsafe { CFFileDescriptorDisableCallBacks(self.0, callback_types) }
69    }
70
71    pub fn valid(&self) -> bool {
72        unsafe { CFFileDescriptorIsValid(self.0) != 0 }
73    }
74
75    pub fn invalidate(&self) {
76        unsafe { CFFileDescriptorInvalidate(self.0) }
77    }
78
79    pub fn to_run_loop_source(&self, order: CFIndex) -> Option<CFRunLoopSource> {
80        unsafe {
81            let source_ref =
82                CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, self.0, order);
83            if source_ref.is_null() {
84                None
85            } else {
86                Some(TCFType::wrap_under_create_rule(source_ref))
87            }
88        }
89    }
90}
91
92impl AsRawFd for CFFileDescriptor {
93    fn as_raw_fd(&self) -> RawFd {
94        unsafe { CFFileDescriptorGetNativeDescriptor(self.0) }
95    }
96}
97
98#[cfg(test)]
99mod test {
100    use super::*;
101    use crate::runloop::CFRunLoop;
102    use core_foundation_sys::base::CFOptionFlags;
103    use core_foundation_sys::runloop::kCFRunLoopDefaultMode;
104    use libc::O_RDWR;
105    use std::ffi::CString;
106    use std::os::raw::c_void;
107
108    #[test]
109    fn test_unconsumed() {
110        let path = CString::new("/dev/null").unwrap();
111        let raw_fd = unsafe { libc::open(path.as_ptr(), O_RDWR, 0) };
112        let cf_fd = CFFileDescriptor::new(raw_fd, false, never_callback, None);
113        assert!(cf_fd.is_some());
114        let cf_fd = cf_fd.unwrap();
115
116        assert!(cf_fd.valid());
117        cf_fd.invalidate();
118        assert!(!cf_fd.valid());
119
120        // close() should succeed
121        assert_eq!(unsafe { libc::close(raw_fd) }, 0);
122    }
123
124    extern "C" fn never_callback(
125        _f: CFFileDescriptorRef,
126        _callback_types: CFOptionFlags,
127        _info_ptr: *mut c_void,
128    ) {
129        unreachable!();
130    }
131
132    struct TestInfo {
133        value: CFOptionFlags,
134    }
135
136    #[test]
137    fn test_callback() {
138        let mut info = TestInfo { value: 0 };
139        let context = CFFileDescriptorContext {
140            version: 0,
141            info: &mut info as *mut _ as *mut c_void,
142            retain: None,
143            release: None,
144            copyDescription: None,
145        };
146
147        let path = CString::new("/dev/null").unwrap();
148        let raw_fd = unsafe { libc::open(path.as_ptr(), O_RDWR, 0) };
149        let cf_fd = CFFileDescriptor::new(raw_fd, true, callback, Some(&context));
150        assert!(cf_fd.is_some());
151        let cf_fd = cf_fd.unwrap();
152
153        assert!(cf_fd.valid());
154
155        let run_loop = CFRunLoop::get_current();
156        let source = CFRunLoopSource::from_file_descriptor(&cf_fd, 0);
157        assert!(source.is_some());
158        unsafe {
159            run_loop.add_source(&source.unwrap(), kCFRunLoopDefaultMode);
160        }
161
162        info.value = 0;
163        cf_fd.enable_callbacks(kCFFileDescriptorReadCallBack);
164        CFRunLoop::run_current();
165        assert_eq!(info.value, kCFFileDescriptorReadCallBack);
166
167        info.value = 0;
168        cf_fd.enable_callbacks(kCFFileDescriptorWriteCallBack);
169        CFRunLoop::run_current();
170        assert_eq!(info.value, kCFFileDescriptorWriteCallBack);
171
172        info.value = 0;
173        cf_fd.disable_callbacks(kCFFileDescriptorReadCallBack | kCFFileDescriptorWriteCallBack);
174
175        cf_fd.invalidate();
176        assert!(!cf_fd.valid());
177    }
178
179    extern "C" fn callback(
180        _f: CFFileDescriptorRef,
181        callback_types: CFOptionFlags,
182        info_ptr: *mut c_void,
183    ) {
184        assert!(!info_ptr.is_null());
185
186        let info: *mut TestInfo = info_ptr as *mut TestInfo;
187
188        unsafe { (*info).value = callback_types };
189
190        CFRunLoop::get_current().stop();
191    }
192}