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 whether 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) -> Option<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    ) -> Option<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            if store.is_null() {
174                None
175            } else {
176                Some(SCDynamicStore::wrap_under_create_rule(store))
177            }
178        }
179    }
180
181    /// Returns the keys that represent the current dynamic store entries that match the specified
182    /// pattern. Or `None` if an error occurred.
183    ///
184    /// `pattern` - A regular expression pattern used to match the dynamic store keys.
185    pub fn get_keys<S: Into<CFString>>(&self, pattern: S) -> Option<CFArray<CFString>> {
186        let cf_pattern = pattern.into();
187        unsafe {
188            let array_ref = SCDynamicStoreCopyKeyList(
189                self.as_concrete_TypeRef(),
190                cf_pattern.as_concrete_TypeRef(),
191            );
192            if array_ref.is_null() {
193                None
194            } else {
195                Some(CFArray::wrap_under_create_rule(array_ref))
196            }
197        }
198    }
199
200    /// Returns the key-value pairs that represent the current internet proxy settings. Or `None` if
201    /// no proxy settings have been defined or if an error occurred.
202    pub fn get_proxies(&self) -> Option<CFDictionary<CFString, CFType>> {
203        unsafe {
204            let dictionary_ref = SCDynamicStoreCopyProxies(self.as_concrete_TypeRef());
205            if !dictionary_ref.is_null() {
206                Some(CFDictionary::wrap_under_create_rule(dictionary_ref))
207            } else {
208                None
209            }
210        }
211    }
212
213    /// If the given key exists in the store, the associated value is returned.
214    ///
215    /// Use `CFPropertyList::downcast_into` to cast the result into the correct type.
216    pub fn get<S: Into<CFString>>(&self, key: S) -> Option<CFPropertyList> {
217        let cf_key = key.into();
218        unsafe {
219            let dict_ref =
220                SCDynamicStoreCopyValue(self.as_concrete_TypeRef(), cf_key.as_concrete_TypeRef());
221            if !dict_ref.is_null() {
222                Some(CFPropertyList::wrap_under_create_rule(dict_ref))
223            } else {
224                None
225            }
226        }
227    }
228
229    /// Sets the value of the given key. Overwrites existing values.
230    /// Returns `true` on success, false on failure.
231    pub fn set<S: Into<CFString>, V: CFPropertyListSubClass>(&self, key: S, value: V) -> bool {
232        self.set_raw(key, &value.into_CFPropertyList())
233    }
234
235    /// Sets the value of the given key. Overwrites existing values.
236    /// Returns `true` on success, false on failure.
237    pub fn set_raw<S: Into<CFString>>(&self, key: S, value: &CFPropertyList) -> bool {
238        let cf_key = key.into();
239        let success = unsafe {
240            SCDynamicStoreSetValue(
241                self.as_concrete_TypeRef(),
242                cf_key.as_concrete_TypeRef(),
243                value.as_concrete_TypeRef(),
244            )
245        };
246        success != 0
247    }
248
249    /// Removes the value of the specified key from the dynamic store.
250    pub fn remove<S: Into<CFString>>(&self, key: S) -> bool {
251        let cf_key = key.into();
252        let success = unsafe {
253            SCDynamicStoreRemoveValue(self.as_concrete_TypeRef(), cf_key.as_concrete_TypeRef())
254        };
255        success != 0
256    }
257
258    /// Specifies a set of keys and key patterns that should be monitored for changes.
259    pub fn set_notification_keys<T1, T2>(
260        &self,
261        keys: &CFArray<T1>,
262        patterns: &CFArray<T2>,
263    ) -> bool {
264        let success = unsafe {
265            SCDynamicStoreSetNotificationKeys(
266                self.as_concrete_TypeRef(),
267                keys.as_concrete_TypeRef(),
268                patterns.as_concrete_TypeRef(),
269            )
270        };
271        success != 0
272    }
273
274    /// Creates a run loop source object that can be added to the application's run loop.
275    pub fn create_run_loop_source(&self) -> Option<CFRunLoopSource> {
276        unsafe {
277            let run_loop_source_ref = SCDynamicStoreCreateRunLoopSource(
278                kCFAllocatorDefault,
279                self.as_concrete_TypeRef(),
280                0,
281            );
282            if run_loop_source_ref.is_null() {
283                None
284            } else {
285                Some(CFRunLoopSource::wrap_under_create_rule(run_loop_source_ref))
286            }
287        }
288    }
289}
290
291/// The raw callback used by the safe `SCDynamicStore` to convert from the `SCDynamicStoreCallBack`
292/// to the `SCDynamicStoreCallBackT`
293unsafe extern "C" fn convert_callback<T>(
294    store_ref: SCDynamicStoreRef,
295    changed_keys_ref: CFArrayRef,
296    context_ptr: *mut c_void,
297) {
298    let store = SCDynamicStore::wrap_under_get_rule(store_ref);
299    let changed_keys = CFArray::<CFString>::wrap_under_get_rule(changed_keys_ref);
300    let context = &mut *(context_ptr as *mut _ as *mut SCDynamicStoreCallBackContext<T>);
301
302    (context.callout)(store, changed_keys, &mut context.info);
303}
304
305// Release function called by core foundation on release of the dynamic store context.
306unsafe extern "C" fn release_callback_context<T>(context_ptr: *const c_void) {
307    // Bring back the context object from raw ptr so it is correctly freed.
308    let _context = Box::from_raw(context_ptr as *mut SCDynamicStoreCallBackContext<T>);
309}