system_configuration/
dynamic_store.rs

1// Copyright 2017 Amagicom AB.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Bindings to [`SCDynamicStore`].
10//!
11//! See the examples directory for examples how to use this module.
12//!
13//! [`SCDynamicStore`]: https://developer.apple.com/documentation/systemconfiguration/scdynamicstore?language=objc
14
15use crate::sys::{
16    dynamic_store::{
17        kSCDynamicStoreUseSessionKeys, SCDynamicStoreCallBack, SCDynamicStoreContext,
18        SCDynamicStoreCopyKeyList, SCDynamicStoreCopyValue, SCDynamicStoreCreateRunLoopSource,
19        SCDynamicStoreCreateWithOptions, SCDynamicStoreGetTypeID, SCDynamicStoreRef,
20        SCDynamicStoreRemoveValue, SCDynamicStoreSetNotificationKeys, SCDynamicStoreSetValue,
21    },
22    dynamic_store_copy_specific::SCDynamicStoreCopyProxies,
23};
24use core_foundation::{
25    array::{CFArray, CFArrayRef},
26    base::{kCFAllocatorDefault, CFType, TCFType},
27    boolean::CFBoolean,
28    dictionary::CFDictionary,
29    propertylist::{CFPropertyList, CFPropertyListSubClass},
30    runloop::CFRunLoopSource,
31    string::CFString,
32};
33use std::{ffi::c_void, ptr};
34
35/// Struct describing the callback happening when a watched value in the dynamic store is changed.
36pub struct SCDynamicStoreCallBackContext<T> {
37    /// The callback function that will be called when a watched value in the dynamic store is
38    /// changed.
39    pub callout: SCDynamicStoreCallBackT<T>,
40
41    /// The argument passed to each `callout` call. Can be used to keep state between
42    /// callbacks.
43    pub info: T,
44}
45
46/// Signature for callback functions getting called when a watched value in the dynamic store is
47/// changed.
48///
49/// This is the safe callback definition, abstracting over the lower level `SCDynamicStoreCallBack`
50/// from the `system-configuration-sys` crate.
51pub type SCDynamicStoreCallBackT<T> =
52    fn(store: SCDynamicStore, changed_keys: CFArray<CFString>, info: &mut T);
53
54/// Builder for [`SCDynamicStore`] sessions.
55///
56/// [`SCDynamicStore`]: struct.SCDynamicStore.html
57pub struct SCDynamicStoreBuilder<T> {
58    name: CFString,
59    session_keys: bool,
60    callback_context: Option<SCDynamicStoreCallBackContext<T>>,
61}
62
63impl SCDynamicStoreBuilder<()> {
64    /// Creates a new builder. `name` is used as the name parameter when creating the
65    /// [`SCDynamicStore`] session.
66    ///
67    /// [`SCDynamicStore`]: struct.SCDynamicStore.html
68    pub fn new<S: Into<CFString>>(name: S) -> Self {
69        SCDynamicStoreBuilder {
70            name: name.into(),
71            session_keys: false,
72            callback_context: None,
73        }
74    }
75}
76
77impl<T> SCDynamicStoreBuilder<T> {
78    /// Set wether or not the created [`SCDynamicStore`] should have session keys or not.
79    /// See [`SCDynamicStoreCreateWithOptions`] for details.
80    ///
81    /// Defaults to `false`.
82    ///
83    /// [`SCDynamicStore`]: struct.SCDynamicStore.html
84    /// [`SCDynamicStoreCreateWithOptions`]: https://developer.apple.com/documentation/systemconfiguration/1437818-scdynamicstorecreatewithoptions?language=objc
85    pub fn session_keys(mut self, session_keys: bool) -> Self {
86        self.session_keys = session_keys;
87        self
88    }
89
90    /// Set a callback context (callback function and data to pass to each callback call).
91    ///
92    /// Defaults to having callbacks disabled.
93    pub fn callback_context<T2>(
94        self,
95        callback_context: SCDynamicStoreCallBackContext<T2>,
96    ) -> SCDynamicStoreBuilder<T2> {
97        SCDynamicStoreBuilder {
98            name: self.name,
99            session_keys: self.session_keys,
100            callback_context: Some(callback_context),
101        }
102    }
103
104    /// Create the dynamic store session.
105    pub fn build(mut self) -> SCDynamicStore {
106        let store_options = self.create_store_options();
107        if let Some(callback_context) = self.callback_context.take() {
108            SCDynamicStore::create(
109                &self.name,
110                &store_options,
111                Some(convert_callback::<T>),
112                &mut self.create_context(callback_context),
113            )
114        } else {
115            SCDynamicStore::create(&self.name, &store_options, None, ptr::null_mut())
116        }
117    }
118
119    fn create_store_options(&self) -> CFDictionary {
120        let key = unsafe { CFString::wrap_under_create_rule(kSCDynamicStoreUseSessionKeys) };
121        let value = CFBoolean::from(self.session_keys);
122        let typed_dict = CFDictionary::from_CFType_pairs(&[(key, value)]);
123        unsafe { CFDictionary::wrap_under_get_rule(typed_dict.as_concrete_TypeRef()) }
124    }
125
126    fn create_context(
127        &self,
128        callback_context: SCDynamicStoreCallBackContext<T>,
129    ) -> SCDynamicStoreContext {
130        // move the callback context struct to the heap and "forget" it.
131        // It will later be brought back into the Rust typesystem and freed in
132        // `release_callback_context`
133        let info_ptr = Box::into_raw(Box::new(callback_context));
134
135        SCDynamicStoreContext {
136            version: 0,
137            info: info_ptr as *mut _ as *mut c_void,
138            retain: None,
139            release: Some(release_callback_context::<T>),
140            copyDescription: None,
141        }
142    }
143}
144
145declare_TCFType! {
146    /// Access to the key-value pairs in the dynamic store of a running system.
147    ///
148    /// Use the [`SCDynamicStoreBuilder`] to create instances of this.
149    ///
150    /// [`SCDynamicStoreBuilder`]: struct.SCDynamicStoreBuilder.html
151    SCDynamicStore, SCDynamicStoreRef
152}
153
154impl_TCFType!(SCDynamicStore, SCDynamicStoreRef, SCDynamicStoreGetTypeID);
155
156impl SCDynamicStore {
157    /// Creates a new session used to interact with the dynamic store maintained by the System
158    /// Configuration server.
159    fn create(
160        name: &CFString,
161        store_options: &CFDictionary,
162        callout: SCDynamicStoreCallBack,
163        context: *mut SCDynamicStoreContext,
164    ) -> Self {
165        unsafe {
166            let store = SCDynamicStoreCreateWithOptions(
167                kCFAllocatorDefault,
168                name.as_concrete_TypeRef(),
169                store_options.as_concrete_TypeRef(),
170                callout,
171                context,
172            );
173            SCDynamicStore::wrap_under_create_rule(store)
174        }
175    }
176
177    /// Returns the keys that represent the current dynamic store entries that match the specified
178    /// pattern. Or `None` if an error occured.
179    ///
180    /// `pattern` - A regular expression pattern used to match the dynamic store keys.
181    pub fn get_keys<S: Into<CFString>>(&self, pattern: S) -> Option<CFArray<CFString>> {
182        let cf_pattern = pattern.into();
183        unsafe {
184            let array_ref = SCDynamicStoreCopyKeyList(
185                self.as_concrete_TypeRef(),
186                cf_pattern.as_concrete_TypeRef(),
187            );
188            if !array_ref.is_null() {
189                Some(CFArray::wrap_under_create_rule(array_ref))
190            } else {
191                None
192            }
193        }
194    }
195
196    /// Returns the key-value pairs that represent the current internet proxy settings. Or `None` if
197    /// no proxy settings have been defined or if an error occured.
198    pub fn get_proxies(&self) -> Option<CFDictionary<CFString, CFType>> {
199        unsafe {
200            let dictionary_ref = SCDynamicStoreCopyProxies(self.as_concrete_TypeRef());
201            if !dictionary_ref.is_null() {
202                Some(CFDictionary::wrap_under_create_rule(dictionary_ref))
203            } else {
204                None
205            }
206        }
207    }
208
209    /// If the given key exists in the store, the associated value is returned.
210    ///
211    /// Use `CFPropertyList::downcast_into` to cast the result into the correct type.
212    pub fn get<S: Into<CFString>>(&self, key: S) -> Option<CFPropertyList> {
213        let cf_key = key.into();
214        unsafe {
215            let dict_ref =
216                SCDynamicStoreCopyValue(self.as_concrete_TypeRef(), cf_key.as_concrete_TypeRef());
217            if !dict_ref.is_null() {
218                Some(CFPropertyList::wrap_under_create_rule(dict_ref))
219            } else {
220                None
221            }
222        }
223    }
224
225    /// Sets the value of the given key. Overwrites existing values.
226    /// Returns `true` on success, false on failure.
227    pub fn set<S: Into<CFString>, V: CFPropertyListSubClass>(&self, key: S, value: V) -> bool {
228        self.set_raw(key, &value.into_CFPropertyList())
229    }
230
231    /// Sets the value of the given key. Overwrites existing values.
232    /// Returns `true` on success, false on failure.
233    pub fn set_raw<S: Into<CFString>>(&self, key: S, value: &CFPropertyList) -> bool {
234        let cf_key = key.into();
235        let success = unsafe {
236            SCDynamicStoreSetValue(
237                self.as_concrete_TypeRef(),
238                cf_key.as_concrete_TypeRef(),
239                value.as_concrete_TypeRef(),
240            )
241        };
242        success != 0
243    }
244
245    /// Removes the value of the specified key from the dynamic store.
246    pub fn remove<S: Into<CFString>>(&self, key: S) -> bool {
247        let cf_key = key.into();
248        let success = unsafe {
249            SCDynamicStoreRemoveValue(self.as_concrete_TypeRef(), cf_key.as_concrete_TypeRef())
250        };
251        success != 0
252    }
253
254    /// Specifies a set of keys and key patterns that should be monitored for changes.
255    pub fn set_notification_keys<T1, T2>(
256        &self,
257        keys: &CFArray<T1>,
258        patterns: &CFArray<T2>,
259    ) -> bool {
260        let success = unsafe {
261            SCDynamicStoreSetNotificationKeys(
262                self.as_concrete_TypeRef(),
263                keys.as_concrete_TypeRef(),
264                patterns.as_concrete_TypeRef(),
265            )
266        };
267        success != 0
268    }
269
270    /// Creates a run loop source object that can be added to the application's run loop.
271    pub fn create_run_loop_source(&self) -> CFRunLoopSource {
272        unsafe {
273            let run_loop_source_ref = SCDynamicStoreCreateRunLoopSource(
274                kCFAllocatorDefault,
275                self.as_concrete_TypeRef(),
276                0,
277            );
278            CFRunLoopSource::wrap_under_create_rule(run_loop_source_ref)
279        }
280    }
281}
282
283/// The raw callback used by the safe `SCDynamicStore` to convert from the `SCDynamicStoreCallBack`
284/// to the `SCDynamicStoreCallBackT`
285unsafe extern "C" fn convert_callback<T>(
286    store_ref: SCDynamicStoreRef,
287    changed_keys_ref: CFArrayRef,
288    context_ptr: *mut c_void,
289) {
290    let store = SCDynamicStore::wrap_under_get_rule(store_ref);
291    let changed_keys = CFArray::<CFString>::wrap_under_get_rule(changed_keys_ref);
292    let context = &mut *(context_ptr as *mut _ as *mut SCDynamicStoreCallBackContext<T>);
293
294    (context.callout)(store, changed_keys, &mut context.info);
295}
296
297// Release function called by core foundation on release of the dynamic store context.
298unsafe extern "C" fn release_callback_context<T>(context_ptr: *const c_void) {
299    // Bring back the context object from raw ptr so it is correctly freed.
300    let _context = Box::from_raw(context_ptr as *mut SCDynamicStoreCallBackContext<T>);
301}