Skip to content

Commit ca23c24

Browse files
committed
Merge branch 'add-dynamic-store-callback-support'
2 parents 29ae251 + 4c80c17 commit ca23c24

File tree

5 files changed

+277
-36
lines changed

5 files changed

+277
-36
lines changed

system-configuration-sys/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ license = "MIT/Apache-2.0"
1010
build = "build.rs"
1111

1212
[dependencies]
13-
core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", version = "0.4.4" }
13+
core-foundation-sys = "0.4.6"
1414

1515
[build-dependencies]
1616
bindgen = "0.31"

system-configuration/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repository = "https://github.com/mullvad/system-configuration-rs"
99
license = "MIT/Apache-2.0"
1010

1111
[dependencies]
12-
core-foundation = { git = "https://github.com/servo/core-foundation-rs", version = "0.4.4" }
13-
core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", version = "0.4.4" }
12+
core-foundation = "0.4.6"
13+
core-foundation-sys = "0.4.6"
1414

15-
system-configuration-sys = { path = "../system-configuration-sys" }
15+
system-configuration-sys = { path = "../system-configuration-sys", version = "0.1" }

system-configuration/src/bin/set_dns.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ use core_foundation::base::TCFType;
99
use core_foundation::dictionary::CFDictionary;
1010
use core_foundation::string::{CFString, CFStringRef};
1111

12-
use system_configuration::dynamic_store::SCDynamicStore;
12+
use system_configuration::dynamic_store::SCDynamicStoreBuilder;
1313

1414
fn main() {
1515
unsafe {
16-
let store = SCDynamicStore::create("my-test-dyn-store");
16+
let store = SCDynamicStoreBuilder::new("my-test-dyn-store").build();
1717
println!("Created dynamic store");
1818

1919
let ipv4_dict = store
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
extern crate system_configuration;
2+
extern crate system_configuration_sys;
3+
4+
extern crate core_foundation;
5+
extern crate core_foundation_sys;
6+
7+
8+
use core_foundation::array::CFArray;
9+
use core_foundation::dictionary::CFDictionary;
10+
use core_foundation::runloop::CFRunLoop;
11+
use core_foundation::string::CFString;
12+
use core_foundation_sys::runloop::kCFRunLoopCommonModes;
13+
14+
use system_configuration::dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder,
15+
SCDynamicStoreCallBackContext};
16+
17+
use std::env;
18+
19+
#[derive(Debug)]
20+
struct Payload {
21+
i: u64,
22+
service_path: CFString,
23+
}
24+
25+
impl Drop for Payload {
26+
fn drop(&mut self) {
27+
println!("Payload Drop");
28+
}
29+
}
30+
31+
fn main() {
32+
let service_id = env::args()
33+
.skip(1)
34+
.next()
35+
.expect("Give service uuid as first argument");
36+
let service_path = CFString::from(&format!("State:/Network/Service/{}/DNS", service_id)[..]);
37+
38+
let watch_keys = CFArray::from_CFTypes(&[service_path.clone()]);
39+
let watch_patterns: CFArray<CFString> = CFArray::from_CFTypes(&[]);
40+
41+
let callback_context = SCDynamicStoreCallBackContext {
42+
callout: my_callback,
43+
info: Payload {
44+
i: 0,
45+
service_path: service_path,
46+
},
47+
};
48+
49+
let store = SCDynamicStoreBuilder::new("my-watch-dns-store")
50+
.callback_context(callback_context)
51+
.build();
52+
println!("Created dynamic store");
53+
54+
if store.set_notification_keys(&watch_keys, &watch_patterns) {
55+
println!("Registered for notifications");
56+
} else {
57+
panic!("Unable to register notifications");
58+
}
59+
let run_loop_source = store.create_run_loop_source();
60+
61+
let run_loop = CFRunLoop::get_current();
62+
run_loop.add_source(&run_loop_source, unsafe { kCFRunLoopCommonModes });
63+
println!("Entering run loop");
64+
CFRunLoop::run_current();
65+
}
66+
67+
fn my_callback(store: SCDynamicStore, _changed_keys: CFArray<CFString>, payload: &mut Payload) {
68+
println!("my_callback2 (payload: {:?})", payload);
69+
payload.i += 1;
70+
71+
if payload.i > 1 {
72+
// Only reset DNS on first callback for now. To not get stuck in infinite loop.
73+
return;
74+
}
75+
76+
let server_addresses_key = CFString::from_static_string("ServerAddresses");
77+
let server_address_1 = CFString::from_static_string("192.168.1.1");
78+
let server_addresses_value = CFArray::from_CFTypes(&[server_address_1]);
79+
80+
let dns_dictionary =
81+
CFDictionary::from_CFType_pairs(&[(server_addresses_key, server_addresses_value)]);
82+
83+
let success = store.set(payload.service_path.clone(), &dns_dictionary);
84+
println!("callback: {}", success);
85+
}

system-configuration/src/dynamic_store.rs

Lines changed: 186 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,137 @@
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
88

9+
use core_foundation::array::{CFArray, CFArrayRef};
910
use core_foundation::base::TCFType;
1011
use core_foundation::boolean::CFBoolean;
1112
use core_foundation::dictionary::CFDictionary;
1213
use core_foundation::propertylist::{CFPropertyList, CFPropertyListSubClass};
14+
use core_foundation::runloop::CFRunLoopSource;
1315
use core_foundation::string::CFString;
1416
use core_foundation_sys::base::{CFRelease, kCFAllocatorDefault};
1517

16-
use system_configuration_sys::dynamic_store::*;
18+
pub use system_configuration_sys::dynamic_store::*;
1719

20+
use std::os::raw::c_void;
1821
use std::ptr;
1922

23+
/// Struct describing the callback happening when a watched value in the dynamic store is changed.
24+
pub struct SCDynamicStoreCallBackContext<T> {
25+
/// The callback function that will be called when a watched value in the dynamic store is
26+
/// changed.
27+
pub callout: SCDynamicStoreCallBackT<T>,
28+
29+
/// The argument passed to each `callout` call. Can be used to keep state between
30+
/// callbacks.
31+
pub info: T,
32+
}
33+
34+
/// Signature for callback functions getting called when a watched value in the dynamic store is
35+
/// changed.
36+
///
37+
/// This is the safe callback definition, abstracting over the lower level `SCDynamicStoreCallBack`
38+
/// from the `system-configuration-sys` crate.
39+
pub type SCDynamicStoreCallBackT<T> = fn(
40+
store: SCDynamicStore,
41+
changed_keys: CFArray<CFString>,
42+
info: &mut T,
43+
);
44+
45+
/// Builder for [`SCDynamicStore`] sessions.
46+
///
47+
/// [`SCDynamicStore`]: struct.SCDynamicStore.html
48+
pub struct SCDynamicStoreBuilder<T> {
49+
name: CFString,
50+
session_keys: bool,
51+
callback_context: Option<SCDynamicStoreCallBackContext<T>>,
52+
}
53+
54+
impl SCDynamicStoreBuilder<()> {
55+
/// Creates a new builder. `name` is used as the name parameter when creating the
56+
/// [`SCDynamicStore`] session.
57+
///
58+
/// [`SCDynamicStore`]: struct.SCDynamicStore.html
59+
pub fn new<S: Into<CFString>>(name: S) -> Self {
60+
SCDynamicStoreBuilder {
61+
name: name.into(),
62+
session_keys: false,
63+
callback_context: None,
64+
}
65+
}
66+
}
67+
68+
impl<T> SCDynamicStoreBuilder<T> {
69+
/// Set wether or not the created [`SCDynamicStore`] should have session keys or not.
70+
/// See [`SCDynamicStoreCreateWithOptions`] for details.
71+
///
72+
/// Defaults to `false`.
73+
///
74+
/// [`SCDynamicStore`]: struct.SCDynamicStore.html
75+
/// [`SCDynamicStoreCreateWithOptions`]: https://developer.apple.com/documentation/systemconfiguration/1437818-scdynamicstorecreatewithoptions?language=objc
76+
pub fn session_keys(mut self, session_keys: bool) -> Self {
77+
self.session_keys = session_keys;
78+
self
79+
}
80+
81+
/// Set a callback context (callback function and data to pass to each callback call).
82+
///
83+
/// Defaults to having callbacks disabled.
84+
pub fn callback_context<T2>(
85+
self,
86+
callback_context: SCDynamicStoreCallBackContext<T2>,
87+
) -> SCDynamicStoreBuilder<T2> {
88+
SCDynamicStoreBuilder {
89+
name: self.name,
90+
session_keys: self.session_keys,
91+
callback_context: Some(callback_context),
92+
}
93+
}
94+
95+
/// Create the dynamic store session.
96+
pub fn build(mut self) -> SCDynamicStore {
97+
let store_options = self.create_store_options();
98+
if let Some(callback_context) = self.callback_context.take() {
99+
SCDynamicStore::create(
100+
&self.name,
101+
&store_options,
102+
Some(convert_callback::<T>),
103+
&mut self.create_context(callback_context),
104+
)
105+
} else {
106+
SCDynamicStore::create(&self.name, &store_options, None, ptr::null_mut())
107+
}
108+
}
109+
110+
fn create_store_options(&self) -> CFDictionary {
111+
let key = unsafe { CFString::wrap_under_create_rule(kSCDynamicStoreUseSessionKeys) };
112+
let value = CFBoolean::from(self.session_keys);
113+
CFDictionary::from_CFType_pairs(&[(key, value)])
114+
}
115+
116+
fn create_context(
117+
&self,
118+
callback_context: SCDynamicStoreCallBackContext<T>,
119+
) -> SCDynamicStoreContext {
120+
// move the callback context struct to the heap and "forget" it.
121+
// It will later be brought back into the Rust typesystem and freed in
122+
// `release_callback_context`
123+
let info_ptr = Box::into_raw(Box::new(callback_context));
124+
125+
SCDynamicStoreContext {
126+
version: 0,
127+
info: info_ptr as *mut _ as *mut c_void,
128+
retain: None,
129+
release: Some(release_callback_context::<T>),
130+
copyDescription: None,
131+
}
132+
}
133+
}
134+
20135
/// Access to the key-value pairs in the dynamic store of a running system.
136+
///
137+
/// Use the [`SCDynamicStoreBuilder`] to create instances of this.
138+
///
139+
/// [`SCDynamicStoreBuilder`]: struct.SCDynamicStoreBuilder.html
21140
pub struct SCDynamicStore(SCDynamicStoreRef);
22141

23142
impl Drop for SCDynamicStore {
@@ -31,39 +150,19 @@ impl_TCFType!(SCDynamicStore, SCDynamicStoreRef, SCDynamicStoreGetTypeID);
31150
impl SCDynamicStore {
32151
/// Creates a new session used to interact with the dynamic store maintained by the System
33152
/// Configuration server.
34-
pub fn create<S: Into<CFString>>(name: S) -> Self {
35-
let cf_name = name.into();
36-
unsafe {
37-
let store = SCDynamicStoreCreate(
38-
kCFAllocatorDefault,
39-
cf_name.as_concrete_TypeRef(),
40-
None,
41-
ptr::null_mut(),
42-
);
43-
SCDynamicStore::wrap_under_create_rule(store)
44-
}
45-
}
46-
47-
/// Creates a new session used to interact with the dynamic store maintained by the System
48-
/// Configuration server. Uses [`SCDynamicStoreCreateWithOptions`] underneath and sets
49-
/// `kSCDynamicStoreUseSessionKeys` to true.
50-
///
51-
/// [`SCDynamicStoreCreateWithOptions`]: https://developer.apple.com/documentation/systemconfiguration/1437818-scdynamicstorecreatewithoptions?language=objc
52-
pub fn create_with_session_keys<S: Into<CFString>>(name: S) -> Self {
53-
let cf_name = name.into();
153+
fn create(
154+
name: &CFString,
155+
store_options: &CFDictionary,
156+
callout: SCDynamicStoreCallBack,
157+
context: *mut SCDynamicStoreContext,
158+
) -> Self {
54159
unsafe {
55-
let store_options = CFDictionary::from_CFType_pairs(&[
56-
(
57-
CFString::wrap_under_create_rule(kSCDynamicStoreUseSessionKeys),
58-
CFBoolean::true_value(),
59-
),
60-
]);
61160
let store = SCDynamicStoreCreateWithOptions(
62161
kCFAllocatorDefault,
63-
cf_name.as_concrete_TypeRef(),
162+
name.as_concrete_TypeRef(),
64163
store_options.as_concrete_TypeRef(),
65-
None,
66-
ptr::null_mut(),
164+
callout,
165+
context,
67166
);
68167
SCDynamicStore::wrap_under_create_rule(store)
69168
}
@@ -108,4 +207,61 @@ impl SCDynamicStore {
108207
};
109208
success != 0
110209
}
210+
211+
pub fn remove<S: Into<CFString>>(&self, key: S) -> bool {
212+
let cf_key = key.into();
213+
let success = unsafe {
214+
SCDynamicStoreRemoveValue(self.as_concrete_TypeRef(), cf_key.as_concrete_TypeRef())
215+
};
216+
success != 0
217+
}
218+
219+
/// Specifies a set of keys and key patterns that should be monitored for changes.
220+
pub fn set_notification_keys<T1, T2>(
221+
&self,
222+
keys: &CFArray<T1>,
223+
patterns: &CFArray<T2>,
224+
) -> bool {
225+
let success = unsafe {
226+
SCDynamicStoreSetNotificationKeys(
227+
self.as_concrete_TypeRef(),
228+
keys.as_concrete_TypeRef(),
229+
patterns.as_concrete_TypeRef(),
230+
)
231+
};
232+
success != 0
233+
}
234+
235+
/// Creates a run loop source object that can be added to the application's run loop.
236+
pub fn create_run_loop_source(&self) -> CFRunLoopSource {
237+
unsafe {
238+
let run_loop_source_ref = SCDynamicStoreCreateRunLoopSource(
239+
kCFAllocatorDefault,
240+
self.as_concrete_TypeRef(),
241+
0,
242+
);
243+
CFRunLoopSource::wrap_under_create_rule(run_loop_source_ref)
244+
}
245+
}
246+
}
247+
248+
249+
/// The raw callback used by the safe `SCDynamicStore` to convert from the `SCDynamicStoreCallBack`
250+
/// to the `SCDynamicStoreCallBackT`
251+
unsafe extern "C" fn convert_callback<T>(
252+
store_ref: SCDynamicStoreRef,
253+
changed_keys_ref: CFArrayRef,
254+
context_ptr: *mut c_void,
255+
) {
256+
let store = SCDynamicStore::wrap_under_get_rule(store_ref);
257+
let changed_keys = CFArray::<CFString>::wrap_under_get_rule(changed_keys_ref);
258+
let context = &mut *(context_ptr as *mut _ as *mut SCDynamicStoreCallBackContext<T>);
259+
260+
(context.callout)(store, changed_keys, &mut context.info);
261+
}
262+
263+
// Release function called by core foundation on release of the dynamic store context.
264+
unsafe extern "C" fn release_callback_context<T>(context_ptr: *const c_void) {
265+
// Bring back the context object from raw ptr so it is correctly freed.
266+
let _context = Box::from_raw(context_ptr as *mut SCDynamicStoreCallBackContext<T>);
111267
}

0 commit comments

Comments
 (0)