yang2/
context.rs

1//
2// Copyright (c) The yang2-rs Core Contributors
3//
4// SPDX-License-Identifier: MIT
5//
6
7//! YANG context.
8
9use bitflags::bitflags;
10use std::collections::HashMap;
11use std::ffi::CString;
12use std::mem::ManuallyDrop;
13use std::os::raw::{c_char, c_void};
14use std::os::unix::ffi::OsStrExt;
15use std::path::Path;
16use std::slice;
17use std::sync::Once;
18
19use crate::error::{Error, Result};
20use crate::iter::{SchemaModules, Set};
21use crate::schema::{SchemaModule, SchemaNode};
22use crate::utils::*;
23use libyang2_sys as ffi;
24
25/// Context of the YANG schemas.
26///
27/// [Official C documentation]
28///
29/// [Official C documentation]: https://netopeer.liberouter.org/doc/libyang/libyang2/html/howto_context.html
30#[derive(Debug, PartialEq)]
31pub struct Context {
32    pub(crate) raw: *mut ffi::ly_ctx,
33}
34
35bitflags! {
36    /// Options to change context behavior.
37    pub struct ContextFlags: u16 {
38        /// All the imported modules of the schema being parsed are implemented.
39        const ALL_IMPLEMENTED = ffi::LY_CTX_ALL_IMPLEMENTED as u16;
40
41        /// Implement all imported modules "referenced" from an implemented
42        /// module. Normally, leafrefs, augment and deviation targets are
43        /// implemented as specified by YANG 1.1. In addition to this, implement
44        /// any modules of nodes referenced by when and must conditions and by
45        /// any default values. Generally, only if all these modules are
46        /// implemented, the explicitly implemented modules can be properly
47        /// used and instantiated in data.
48        const REF_IMPLEMENTED = ffi::LY_CTX_REF_IMPLEMENTED as u16;
49
50        /// Do not internally implement ietf-yang-library module. This option
51        /// cannot be changed on existing context.
52        const NO_YANGLIBRARY = ffi::LY_CTX_NO_YANGLIBRARY as u16;
53
54        /// Do not search for schemas in context's searchdirs neither in current
55        /// working directory.
56        const DISABLE_SEARCHDIRS = ffi::LY_CTX_DISABLE_SEARCHDIRS as u16;
57
58        /// Do not automatically search for schemas in current working
59        /// directory, which is by default searched automatically (despite not
60        /// recursively).
61        const DISABLE_SEARCHDIR_CWD = ffi::LY_CTX_DISABLE_SEARCHDIR_CWD as u16;
62    }
63}
64
65/// Embedded module key containing the module/submodule name and optional
66/// revision.
67#[derive(Debug, Eq, Hash, PartialEq)]
68pub struct EmbeddedModuleKey {
69    mod_name: &'static str,
70    mod_rev: Option<&'static str>,
71    submod_name: Option<&'static str>,
72    submod_rev: Option<&'static str>,
73}
74
75/// A hashmap containing embedded YANG modules.
76pub type EmbeddedModules = HashMap<EmbeddedModuleKey, &'static str>;
77
78// ===== impl Context =====
79
80impl Context {
81    /// Create libyang context.
82    ///
83    /// Context is used to hold all information about schemas. Usually, the
84    /// application is supposed to work with a single context in which
85    /// libyang is holding all schemas (and other internal information)
86    /// according to which the data trees will be processed and validated.
87    pub fn new(options: ContextFlags) -> Result<Context> {
88        static INIT: Once = Once::new();
89        let mut context = std::ptr::null_mut();
90        let ctx_ptr = &mut context;
91
92        // Initialization routine that is called only once when the first YANG
93        // context is created.
94        INIT.call_once(|| {
95            // Disable automatic logging to stderr in order to give users more
96            // control over the handling of errors.
97            unsafe { ffi::ly_log_options(ffi::LY_LOSTORE_LAST) };
98        });
99
100        let ret = unsafe {
101            ffi::ly_ctx_new(std::ptr::null(), options.bits(), ctx_ptr)
102        };
103        if ret != ffi::LY_ERR::LY_SUCCESS {
104            // Need to construct error structure by hand.
105            return Err(Error {
106                errcode: ret,
107                msg: None,
108                path: None,
109                apptag: None,
110            });
111        }
112
113        Ok(Context { raw: context })
114    }
115
116    /// Returns a mutable raw pointer to the underlying C library representation
117    /// of the libyang context.
118    pub fn into_raw(self) -> *mut ffi::ly_ctx {
119        ManuallyDrop::new(self).raw
120    }
121
122    /// Add the search path into libyang context.
123    pub fn set_searchdir<P: AsRef<Path>>(
124        &mut self,
125        search_dir: P,
126    ) -> Result<()> {
127        let search_dir =
128            CString::new(search_dir.as_ref().as_os_str().as_bytes()).unwrap();
129        let ret =
130            unsafe { ffi::ly_ctx_set_searchdir(self.raw, search_dir.as_ptr()) };
131        if ret != ffi::LY_ERR::LY_SUCCESS {
132            return Err(Error::new(self));
133        }
134
135        Ok(())
136    }
137
138    /// Clean the search path from the libyang context.
139    ///
140    /// To remove the recently added search path(s), use
141    /// Context::unset_searchdir_last().
142    pub fn unset_searchdir<P: AsRef<Path>>(
143        &mut self,
144        search_dir: P,
145    ) -> Result<()> {
146        let search_dir =
147            CString::new(search_dir.as_ref().as_os_str().as_bytes()).unwrap();
148        let ret = unsafe {
149            ffi::ly_ctx_unset_searchdir(self.raw, search_dir.as_ptr())
150        };
151        if ret != ffi::LY_ERR::LY_SUCCESS {
152            return Err(Error::new(self));
153        }
154
155        Ok(())
156    }
157
158    /// Clean all search paths from the libyang context.
159    pub fn unset_searchdirs(&mut self) -> Result<()> {
160        let ret =
161            unsafe { ffi::ly_ctx_unset_searchdir(self.raw, std::ptr::null()) };
162        if ret != ffi::LY_ERR::LY_SUCCESS {
163            return Err(Error::new(self));
164        }
165
166        Ok(())
167    }
168
169    /// Remove the least recently added search path(s) from the libyang context.
170    ///
171    /// To remove a specific search path by its value, use
172    /// Context::unset_searchdir().
173    pub fn unset_searchdir_last(&mut self, count: u32) -> Result<()> {
174        let ret = unsafe { ffi::ly_ctx_unset_searchdir_last(self.raw, count) };
175        if ret != ffi::LY_ERR::LY_SUCCESS {
176            return Err(Error::new(self));
177        }
178
179        Ok(())
180    }
181
182    /// Set hash map containing embedded YANG modules, which are loaded on
183    /// demand.
184    pub fn set_embedded_modules(&mut self, modules: &EmbeddedModules) {
185        unsafe {
186            ffi::ly_ctx_set_module_imp_clb(
187                self.raw,
188                Some(ly_module_import_cb),
189                modules as *const _ as *mut c_void,
190            )
191        };
192    }
193
194    /// Remove all embedded modules from the libyang context.
195    pub fn unset_embedded_modules(&mut self) {
196        unsafe {
197            ffi::ly_ctx_set_module_imp_clb(self.raw, None, std::ptr::null_mut())
198        };
199    }
200
201    /// Get the currently set context's options.
202    pub fn get_options(&self) -> ContextFlags {
203        let options = unsafe { ffi::ly_ctx_get_options(self.raw) };
204        ContextFlags::from_bits_truncate(options)
205    }
206
207    /// Set some of the context's options.
208    pub fn set_options(&mut self, options: ContextFlags) -> Result<()> {
209        let ret = unsafe { ffi::ly_ctx_set_options(self.raw, options.bits()) };
210        if ret != ffi::LY_ERR::LY_SUCCESS {
211            return Err(Error::new(self));
212        }
213
214        Ok(())
215    }
216
217    /// Unset some of the context's options.
218    pub fn unset_options(&mut self, options: ContextFlags) -> Result<()> {
219        let ret =
220            unsafe { ffi::ly_ctx_unset_options(self.raw, options.bits()) };
221        if ret != ffi::LY_ERR::LY_SUCCESS {
222            return Err(Error::new(self));
223        }
224
225        Ok(())
226    }
227
228    /// Get current ID of the modules set.
229    pub fn get_module_set_id(&self) -> u16 {
230        unsafe { ffi::ly_ctx_get_change_count(self.raw) }
231    }
232
233    /// Get YANG module of the given name and revision.
234    ///
235    /// If the revision is not specified, the schema with no revision is
236    /// returned (if it is present in the context).
237    pub fn get_module(
238        &self,
239        name: &str,
240        revision: Option<&str>,
241    ) -> Option<SchemaModule<'_>> {
242        let name = CString::new(name).unwrap();
243        let revision_cstr;
244
245        let revision_ptr = match revision {
246            Some(revision) => {
247                revision_cstr = CString::new(revision).unwrap();
248                revision_cstr.as_ptr()
249            }
250            None => std::ptr::null(),
251        };
252        let module = unsafe {
253            ffi::ly_ctx_get_module(self.raw, name.as_ptr(), revision_ptr)
254        };
255        if module.is_null() {
256            return None;
257        }
258
259        Some(unsafe { SchemaModule::from_raw(self, module) })
260    }
261
262    /// Get the latest revision of the YANG module specified by its name.
263    ///
264    /// YANG modules with no revision are supposed to be the oldest one.
265    pub fn get_module_latest(&self, name: &str) -> Option<SchemaModule<'_>> {
266        let name = CString::new(name).unwrap();
267        let module =
268            unsafe { ffi::ly_ctx_get_module_latest(self.raw, name.as_ptr()) };
269        if module.is_null() {
270            return None;
271        }
272
273        Some(unsafe { SchemaModule::from_raw(self, module) })
274    }
275
276    /// Get the (only) implemented YANG module specified by its name.
277    pub fn get_module_implemented(
278        &self,
279        name: &str,
280    ) -> Option<SchemaModule<'_>> {
281        let name = CString::new(name).unwrap();
282        let module = unsafe {
283            ffi::ly_ctx_get_module_implemented(self.raw, name.as_ptr())
284        };
285        if module.is_null() {
286            return None;
287        }
288
289        Some(unsafe { SchemaModule::from_raw(self, module) })
290    }
291
292    /// YANG module of the given namespace and revision.
293    ///
294    /// If the revision is not specified, the schema with no revision is
295    /// returned (if it is present in the context).
296    pub fn get_module_ns(
297        &self,
298        ns: &str,
299        revision: Option<&str>,
300    ) -> Option<SchemaModule<'_>> {
301        let ns = CString::new(ns).unwrap();
302        let revision_cstr;
303
304        let revision_ptr = match revision {
305            Some(revision) => {
306                revision_cstr = CString::new(revision).unwrap();
307                revision_cstr.as_ptr()
308            }
309            None => std::ptr::null(),
310        };
311
312        let module = unsafe {
313            ffi::ly_ctx_get_module_ns(self.raw, ns.as_ptr(), revision_ptr)
314        };
315        if module.is_null() {
316            return None;
317        }
318
319        Some(unsafe { SchemaModule::from_raw(self, module) })
320    }
321
322    /// Get the latest revision of the YANG module specified by its namespace.
323    ///
324    /// YANG modules with no revision are supposed to be the oldest one.
325    pub fn get_module_latest_ns(&self, ns: &str) -> Option<SchemaModule<'_>> {
326        let ns = CString::new(ns).unwrap();
327        let module =
328            unsafe { ffi::ly_ctx_get_module_latest_ns(self.raw, ns.as_ptr()) };
329        if module.is_null() {
330            return None;
331        }
332
333        Some(unsafe { SchemaModule::from_raw(self, module) })
334    }
335
336    /// Get the (only) implemented YANG module specified by its namespace.
337    pub fn get_module_implemented_ns(
338        &self,
339        ns: &str,
340    ) -> Option<SchemaModule<'_>> {
341        let ns = CString::new(ns).unwrap();
342        let module = unsafe {
343            ffi::ly_ctx_get_module_implemented_ns(self.raw, ns.as_ptr())
344        };
345        if module.is_null() {
346            return None;
347        }
348
349        Some(unsafe { SchemaModule::from_raw(self, module) })
350    }
351
352    /// Get list of loaded modules.
353    ///
354    /// Internal modules (loaded during the context creation) can be skipped by
355    /// setting "skip_internal" to true.
356    pub fn modules(&self, skip_internal: bool) -> SchemaModules<'_> {
357        SchemaModules::new(self, skip_internal)
358    }
359
360    /// Returns an iterator over all data nodes from all modules in the YANG
361    /// context (depth-first search algorithm).
362    pub fn traverse(&self) -> impl Iterator<Item = SchemaNode<'_>> {
363        self.modules(false).flat_map(|module| module.traverse())
364    }
365
366    /// Learn the number of internal modules of the context. Internal modules is
367    /// considered one that was loaded during the context creation.
368    pub fn internal_module_count(&self) -> u32 {
369        unsafe { ffi::ly_ctx_internal_modules_count(self.raw) }
370    }
371
372    /// Try to find the model in the searchpaths and load it.
373    ///
374    /// The context itself is searched for the requested module first. If
375    /// revision is not specified (the module of the latest revision is
376    /// requested) and there is implemented revision of the requested module
377    /// in the context, this implemented revision is returned despite there
378    /// might be a newer revision. This behavior is caused by the fact that
379    /// it is not possible to have multiple implemented revisions of
380    /// the same module in the context.
381    ///
382    /// If the revision is not specified, the latest revision is loaded.
383    ///
384    /// The `features` parameter specifies the module features that should be
385    /// enabled. If let empty, no features are enabled. The feature string '*'
386    /// enables all module features.
387    pub fn load_module(
388        &mut self,
389        name: &str,
390        revision: Option<&str>,
391        features: &[&str],
392    ) -> Result<SchemaModule<'_>> {
393        let name = CString::new(name).unwrap();
394        let revision_cstr;
395        let mut features_ptr;
396
397        // Prepare revision string.
398        let revision_ptr = match revision {
399            Some(revision) => {
400                revision_cstr = CString::new(revision).unwrap();
401                revision_cstr.as_ptr()
402            }
403            None => std::ptr::null(),
404        };
405
406        // Prepare features array.
407        let features_cstr = features
408            .iter()
409            .map(|feature| CString::new(*feature).unwrap())
410            .collect::<Vec<_>>();
411        features_ptr = features_cstr
412            .iter()
413            .map(|feature| feature.as_ptr())
414            .collect::<Vec<_>>();
415        features_ptr.push(std::ptr::null());
416
417        let module = unsafe {
418            ffi::ly_ctx_load_module(
419                self.raw,
420                name.as_ptr(),
421                revision_ptr,
422                features_ptr.as_mut_ptr(),
423            )
424        };
425        if module.is_null() {
426            return Err(Error::new(self));
427        }
428
429        Ok(unsafe { SchemaModule::from_raw(self, module as *mut _) })
430    }
431
432    /// Evaluate an xpath expression on schema nodes.
433    pub fn find_xpath(&self, path: &str) -> Result<Set<'_, SchemaNode<'_>>> {
434        let path = CString::new(path).unwrap();
435        let mut set = std::ptr::null_mut();
436        let set_ptr = &mut set;
437        let options = 0u32;
438
439        let ret = unsafe {
440            ffi::lys_find_xpath(
441                self.raw,
442                std::ptr::null(),
443                path.as_ptr(),
444                options,
445                set_ptr,
446            )
447        };
448        if ret != ffi::LY_ERR::LY_SUCCESS {
449            return Err(Error::new(self));
450        }
451
452        let rnodes_count = unsafe { (*set).count } as usize;
453        let slice = if rnodes_count == 0 {
454            &[]
455        } else {
456            let rnodes = unsafe { (*set).__bindgen_anon_1.snodes };
457            unsafe { slice::from_raw_parts(rnodes, rnodes_count) }
458        };
459
460        Ok(Set::new(self, slice))
461    }
462
463    /// Get a schema node based on the given data path (JSON format).
464    pub fn find_path(&self, path: &str) -> Result<SchemaNode<'_>> {
465        let path = CString::new(path).unwrap();
466
467        let rnode = unsafe {
468            ffi::lys_find_path(self.raw, std::ptr::null(), path.as_ptr(), 0)
469        };
470        if rnode.is_null() {
471            return Err(Error::new(self));
472        }
473
474        Ok(unsafe { SchemaNode::from_raw(self, rnode as *mut _) })
475    }
476}
477
478unsafe impl Send for Context {}
479unsafe impl Sync for Context {}
480
481impl Drop for Context {
482    fn drop(&mut self) {
483        unsafe { ffi::ly_ctx_destroy(self.raw) };
484    }
485}
486
487// ===== impl EmbeddedModuleKey =====
488
489impl EmbeddedModuleKey {
490    pub fn new(
491        mod_name: &'static str,
492        mod_rev: Option<&'static str>,
493        submod_name: Option<&'static str>,
494        submod_rev: Option<&'static str>,
495    ) -> EmbeddedModuleKey {
496        EmbeddedModuleKey {
497            mod_name,
498            mod_rev,
499            submod_name,
500            submod_rev,
501        }
502    }
503}
504
505unsafe impl<'a> Binding<'a> for Context {
506    type CType = ffi::ly_ctx;
507    type Container = ();
508
509    unsafe fn from_raw(_: &'a Self::Container, raw: *mut Self::CType) -> Self {
510        Self { raw }
511    }
512}
513
514// ===== helper functions =====
515
516fn find_embedded_module<'a>(
517    modules: &'a EmbeddedModules,
518    mod_name: &'a str,
519    mod_rev: Option<&'a str>,
520    submod_name: Option<&'a str>,
521    submod_rev: Option<&'a str>,
522) -> Option<(&'a EmbeddedModuleKey, &'a &'a str)> {
523    modules.iter().find(|(key, _)| {
524        *key.mod_name == *mod_name
525            && (mod_rev.is_none() || key.mod_rev == mod_rev)
526            && match submod_name {
527                Some(submod_name) => {
528                    key.submod_name == Some(submod_name)
529                        && (submod_rev.is_none()
530                            || key.submod_rev == submod_rev)
531                }
532                None => key.submod_name.is_none(),
533            }
534    })
535}
536
537unsafe extern "C" fn ly_module_import_cb(
538    mod_name: *const c_char,
539    mod_rev: *const c_char,
540    submod_name: *const c_char,
541    submod_rev: *const c_char,
542    user_data: *mut c_void,
543    format: *mut ffi::LYS_INFORMAT::Type,
544    module_data: *mut *const c_char,
545    _free_module_data: *mut ffi::ly_module_imp_data_free_clb,
546) -> ffi::LY_ERR::Type {
547    let modules = &*(user_data as *const EmbeddedModules);
548    let mod_name = char_ptr_to_str(mod_name);
549    let mod_rev = char_ptr_to_opt_str(mod_rev);
550    let submod_name = char_ptr_to_opt_str(submod_name);
551    let submod_rev = char_ptr_to_opt_str(submod_rev);
552
553    if let Some((_emod_key, emod_data)) = find_embedded_module(
554        modules,
555        mod_name,
556        mod_rev,
557        submod_name,
558        submod_rev,
559    ) {
560        let data = CString::new(*emod_data).unwrap();
561
562        *format = ffi::LYS_INFORMAT::LYS_IN_YANG;
563        *module_data = data.as_ptr();
564        std::mem::forget(data);
565        return ffi::LY_ERR::LY_SUCCESS;
566    }
567
568    ffi::LY_ERR::LY_ENOTFOUND
569}