From 016df5f1b0d7a5034602d6b182f4d332f117b96e Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 20:16:50 -0700 Subject: [PATCH 01/20] Add a binding for aug_insert --- src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 52f8d30..0cc6e69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ use augeas_sys::*; use std::ptr; use std::mem::transmute; use std::ffi::CString; -use std::os::raw::c_char; +use std::os::raw::{c_char,c_int}; use std::convert::From; pub mod error; @@ -26,6 +26,21 @@ pub struct Augeas { ptr: *mut augeas, } +#[derive(Clone, Copy)] +pub enum Position { + Before, + After +} + +impl From for c_int { + fn from(pos: Position) -> Self { + match pos { + Position::Before => 1, + Position::After => 0 + } + } +} + impl Augeas { pub fn init<'a>(root: impl Into>, loadpath: &str, flags: Flags) -> Result { let ref root = match root.into() { @@ -40,7 +55,7 @@ impl Augeas { let loadpath = loadpath.as_ptr(); let flags = flags.bits(); let augeas = unsafe { aug_init(root, loadpath, flags) }; - + if augeas.is_null() { let message = String::from("Failed to initialize Augeas"); return Err(Error::Augeas(AugeasError::new_no_mem(message))); @@ -108,6 +123,14 @@ impl Augeas { unsafe { aug_set(self.ptr, path_c.as_ptr(), value_c.as_ptr()) }; self.make_result(()) } + + pub fn insert(&mut self, path: &str, label: &str, pos:Position) -> Result<()> { + let path = CString::new(path.as_bytes())?; + let label = CString::new(label.as_bytes())?; + + unsafe { aug_insert(self.ptr, path.as_ptr(), label.as_ptr(), c_int::from(pos)) }; + self.make_result(()) + } } impl Augeas { @@ -177,6 +200,19 @@ fn matches_test() { } } +#[test] +fn insert_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + aug.insert("etc/passwd/root", "before", Position::Before).unwrap(); + aug.insert("etc/passwd/root", "after", Position::After).unwrap(); + let users = aug.matches("etc/passwd/*").unwrap(); + assert_eq!(["/files/etc/passwd/before", + "/files/etc/passwd/root", + "/files/etc/passwd/after"], + users[0..3]); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From 5d2a099b4f3092b379f1387f33ab28cd861c0055 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 20:17:05 -0700 Subject: [PATCH 02/20] Add a binding for aug_rm --- src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0cc6e69..e21b9b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,17 @@ impl Augeas { unsafe { aug_insert(self.ptr, path.as_ptr(), label.as_ptr(), c_int::from(pos)) }; self.make_result(()) } + + pub fn rm(&mut self, path: &str) -> Result { + let path = CString::new(path.as_bytes())?; + let r = unsafe { + aug_rm(self.ptr, path.as_ptr()) + }; + // coercing i32 to u32 is fine here since r is only negative + // when an error occurred and make_result notices that from + // the result of aug_error + self.make_result(r as u32) + } } impl Augeas { @@ -213,6 +224,17 @@ fn insert_test() { users[0..3]); } +#[test] +fn rm_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + let e = aug.rm("/augeas["); + assert!(e.is_err()); + + let r = aug.rm("etc/passwd").unwrap(); + assert_eq!(64, r); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From faf8b71dfca5548f6685d6a63de6c42756721fb3 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 20:17:29 -0700 Subject: [PATCH 03/20] Add binding for aug_mv --- src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e21b9b7..9391506 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,6 +142,14 @@ impl Augeas { // the result of aug_error self.make_result(r as u32) } + + pub fn mv(&mut self, src: &str, dst: &str) -> Result<()> { + let src = CString::new(src)?; + let dst = CString::new(dst)?; + + unsafe { aug_mv(self.ptr, src.as_ptr(), dst.as_ptr()) }; + self.make_result(()) + } } impl Augeas { @@ -235,6 +243,18 @@ fn rm_test() { assert_eq!(64, r); } +#[test] +fn mv_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + let e = aug.mv("etc/passwd", "etc/passwd/root"); + assert!(e.is_err()); + + aug.mv("etc/passwd", "etc/other").unwrap(); + assert_eq!(0, aug.matches("etc/passwd").unwrap().len()); + assert_eq!(1, aug.matches("etc/other").unwrap().len()); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From c123f0e4b9c5c433f7a80e44a54566f2e689bc5c Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 20:17:40 -0700 Subject: [PATCH 04/20] Add a count method that avoids the allocations of `matches` --- src/lib.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9391506..f3bf4de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,6 +111,14 @@ impl Augeas { } } + pub fn count(&self, path: &str) -> Result { + let path = CString::new(path)?; + + let r = unsafe { aug_match(self.ptr, path.as_ptr(), ptr::null_mut()) }; + + self.make_result(r as u32) + } + pub fn save(&mut self) -> Result<()> { unsafe { aug_save(self.ptr) }; self.make_result(()) @@ -212,11 +220,12 @@ fn matches_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); let users = aug.matches("etc/passwd/*").unwrap(); + let count = aug.count("etc/passwd/*").unwrap(); - println!("Users in passwd:"); - for user in users.iter() { - println!("{}", &aug.label(&user).unwrap().unwrap_or("unknown".to_string())); - } + assert_eq!(9, users.len()); + assert_eq!(9, count); + assert_eq!("/files/etc/passwd/root", users[0]); + assert_eq!("/files/etc/passwd/nobody", users[8]); } #[test] @@ -251,8 +260,8 @@ fn mv_test() { assert!(e.is_err()); aug.mv("etc/passwd", "etc/other").unwrap(); - assert_eq!(0, aug.matches("etc/passwd").unwrap().len()); - assert_eq!(1, aug.matches("etc/other").unwrap().len()); + assert_eq!(0, aug.count("etc/passwd").unwrap()); + assert_eq!(1, aug.count("etc/other").unwrap()); } #[test] From b80035fb179a9c84496b4d542919bbb8d956db62 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 24 Oct 2018 21:14:24 -0700 Subject: [PATCH 05/20] Add bindings for aug_defvar and aug_defnode --- src/lib.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index f3bf4de..217d5e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,25 @@ impl Augeas { unsafe { aug_mv(self.ptr, src.as_ptr(), dst.as_ptr()) }; self.make_result(()) } + + pub fn defvar(&mut self, name: &str, expr: &str) -> Result<()> { + let name = CString::new(name)?; + let expr = CString::new(expr)?; + + unsafe { aug_defvar(self.ptr, name.as_ptr(), expr.as_ptr()) }; + self.make_result(()) + } + + pub fn defnode(&mut self, name: &str, expr: &str, value: &str) -> Result { + let name = CString::new(name)?; + let expr = CString::new(expr)?; + let value = CString::new(value)?; + let mut cr : i32 = 0; + + unsafe { aug_defnode(self.ptr, name.as_ptr(), expr.as_ptr(), + value.as_ptr(), &mut cr) }; + self.make_result(cr == 1) + } } impl Augeas { @@ -264,6 +283,30 @@ fn mv_test() { assert_eq!(1, aug.count("etc/other").unwrap()); } +#[test] +fn defvar_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + aug.defvar("x", "etc/passwd/*").unwrap(); + let n = aug.count("$x").unwrap(); + + assert_eq!(9, n); +} + +#[test] +fn defnode_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + let created = aug.defnode("y", "etc/notthere", "there").unwrap(); + assert!(created); + + let there = aug.get("$y").unwrap(); + assert_eq!("there", there.expect("failed to get etc/notthere")); + + let created = aug.defnode("z", "etc/passwd", "there").unwrap(); + assert!(! created); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From c07416761a3417bfada8f30cf5fa7f9202229091 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 24 Oct 2018 21:18:23 -0700 Subject: [PATCH 06/20] Add binding for aug_load --- src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 217d5e9..a6ebf61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,6 +177,12 @@ impl Augeas { value.as_ptr(), &mut cr) }; self.make_result(cr == 1) } + + pub fn load(&mut self) -> Result<()> { + unsafe { aug_load(self.ptr) }; + self.make_result(()) + } + } impl Augeas { @@ -307,6 +313,16 @@ fn defnode_test() { assert!(! created); } +#[test] +fn load_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + aug.set("etc/passwd/root/uid", "42").unwrap(); + aug.load().unwrap(); + let uid = aug.get("etc/passwd/root/uid").unwrap(); + assert_eq!("0", uid.expect("expected value for root/uid")); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From 17b3092eebdac497c7d668784278c0252745fcb1 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 20:18:17 -0700 Subject: [PATCH 07/20] Add binding for aug_setm --- src/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a6ebf61..2ccb1aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -183,6 +183,15 @@ impl Augeas { self.make_result(()) } + pub fn setm(&mut self, base: &str, sub: &str, value: &str) -> Result<(u32)> { + let base = CString::new(base)?; + let sub = CString::new(sub)?; + let value = CString::new(value)?; + + let r = unsafe { aug_setm(self.ptr, base.as_ptr(), sub.as_ptr(), + value.as_ptr()) }; + self.make_result(r as u32) + } } impl Augeas { @@ -323,6 +332,14 @@ fn load_test() { assert_eq!("0", uid.expect("expected value for root/uid")); } +#[test] +fn setm_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + let count = aug.setm("etc/passwd", "*/shell", "/bin/zsh").unwrap(); + assert_eq!(9, count); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From a8abb0c1ba61c5e27de7e27befa1a6ccb89b4715 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 18:24:32 -0700 Subject: [PATCH 08/20] Add binding for aug_span --- src/lib.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2ccb1aa..3dcbc3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ use std::mem::transmute; use std::ffi::CString; use std::os::raw::{c_char,c_int}; use std::convert::From; +use std::ops::Range; pub mod error; pub use error::Error; @@ -41,6 +42,25 @@ impl From for c_int { } } +#[derive(Debug)] +pub struct Span { + pub label : Range, + pub value : Range, + pub span : Range, + pub filename : Option +} + +impl Span { + fn new() -> Span { + Span { + label: 0..0, + value: 0..0, + span: 0..0, + filename: None + } + } +} + impl Augeas { pub fn init<'a>(root: impl Into>, loadpath: &str, flags: Flags) -> Result { let ref root = match root.into() { @@ -192,6 +212,33 @@ impl Augeas { value.as_ptr()) }; self.make_result(r as u32) } + + pub fn span(&self, path: &str) -> Result> { + let path = CString::new(path)?; + let mut filename : *mut c_char = ptr::null_mut(); + let mut result = Span::new(); + + unsafe { + aug_span(self.ptr, path.as_ptr(), &mut filename, + &mut result.label.start, &mut result.label.end, + &mut result.value.start, &mut result.value.end, + &mut result.span.start, &mut result.span.end); + } + + let err = unsafe { aug_error(self.ptr) }; + let err = ErrorCode::from_raw(err as _); + if err != ErrorCode::NoError { + if err == ErrorCode::NoSpan { + return Ok(None); + } else { + return self.make_result(None); + } + } + + result.filename = unsafe { ptr_to_string(filename) }; + unsafe { libc::free(filename as *mut libc::c_void) }; + Ok(Some(result)) + } } impl Augeas { @@ -340,6 +387,26 @@ fn setm_test() { assert_eq!(9, count); } +#[test] +fn span_test() { + let aug = Augeas::init("tests/test_root", "", Flags::EnableSpan).unwrap(); + + // happy path + let span = aug.span("etc/passwd/root").unwrap().unwrap(); + assert_eq!(0..4, span.label); + assert_eq!(0..0, span.value); + assert_eq!(0..32, span.span); + assert_eq!("tests/test_root/etc/passwd", span.filename.unwrap()); + + // no span info associated with node + let span = aug.span("/augeas/load").unwrap(); + assert!(span.is_none()); + + // too many matches + let span = aug.span("etc/passwd/*"); + assert!(span.is_err()); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From 709d62144fd6dd17ba96e677eb12ea5632760794 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 20:18:54 -0700 Subject: [PATCH 09/20] Add binding for aug_text_store and aug_text_retrieve --- src/error.rs | 34 +++++++++++++++++++--------- src/lib.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index 22458de..8285a3f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,23 +7,16 @@ use augeas_sys::*; #[derive(Clone,PartialEq,Debug)] pub enum Error { Augeas(AugeasError), + Parse(ParseError), Nul(NulError) } -impl ::std::error::Error for Error { - fn description(&self) -> &str { - match *self { - Error::Augeas(ref err) => err.description(), - Error::Nul(ref err) => err.description() - } - } -} - impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Augeas(ref err) => err.fmt(f), - Error::Nul(ref err) => err.fmt(f) + Error::Nul(ref err) => err.fmt(f), + Error::Parse(ref err) => err.fmt(f) } } } @@ -145,3 +138,24 @@ impl ErrorCode { impl Default for ErrorCode { fn default() -> ErrorCode { ErrorCode::NoError } } + +impl From for Error { + fn from(kind: String) -> Error { + Error::Parse(ParseError { + kind: kind + }) + } +} + +impl fmt::Display for ParseError { + fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result { + write!(f, "parse error of kind {}", self.kind) + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct ParseError { + // There's a lot more information we can/should pull out of the + // tree when parsing goes wrong + pub kind : String +} diff --git a/src/lib.rs b/src/lib.rs index 3dcbc3b..6c7cfdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -239,6 +239,46 @@ impl Augeas { unsafe { libc::free(filename as *mut libc::c_void) }; Ok(Some(result)) } + + pub fn text_store(&mut self, lens: &str, node: &str, path: &str) -> Result<()> { + let err_path = format!("/augeas/text{}/error", path); + + let lens = CString::new(lens)?; + let node = CString::new(node)?; + let path = CString::new(path)?; + + unsafe { aug_text_store(self.ptr, lens.as_ptr(), node.as_ptr(), + path.as_ptr()) }; + + let err = self.get(&err_path)?; + if let Some(kind) = err { + return Err(Error::from(kind)); + } + self.make_result(()) + } + + pub fn text_retrieve(&mut self, lens: &str, + node_in: &str, path: &str, + node_out: &str) -> Result<()> { + let err_path = format!("/augeas/text{}/error", path); + + let lens = CString::new(lens)?; + let node_in = CString::new(node_in)?; + let path = CString::new(path)?; + let node_out = CString::new(node_out)?; + + unsafe { aug_text_retrieve(self.ptr, lens.as_ptr(), + node_in.as_ptr(), path.as_ptr(), + node_out.as_ptr()) }; + + let err = self.get(&err_path)?; + if let Some(kind) = err { + return Err(Error::from(kind)); + } + + self.make_result(()) + } + } impl Augeas { @@ -407,6 +447,28 @@ fn span_test() { assert!(span.is_err()); } +#[test] +fn store_retrieve_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + aug.set("/text/in", "alex:x:12:12:Alex:/home/alex:/bin/sh\n").unwrap(); + aug.text_store("Passwd.lns", "/text/in", "/stored").unwrap(); + aug.set("/stored/alex/uid", "17").unwrap(); + + aug.text_retrieve("Passwd.lns", "/text/in", "/stored", "/text/out").unwrap(); + let text = aug.get("/text/out").unwrap().unwrap(); + assert_eq!("alex:x:17:12:Alex:/home/alex:/bin/sh\n", text); + + // Invalidate the tree; 'shell' must be present + aug.rm("/stored/alex/shell").unwrap(); + let err = aug.text_retrieve("Passwd.lns", "/text/in", "/stored", "/text/out").err().unwrap(); + assert_eq!("parse error of kind put_failed", format!("{}", err)); + + aug.set("/text/in", "alex:invalid passwd entry").unwrap(); + let err = aug.text_store("Passwd.lns", "/text/in", "/stored").err().unwrap(); + assert_eq!("parse error of kind parse_failed", format!("{}", err)); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From 223e06f89f42910214f7dce8aeea0b62c9c12601 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 18:38:24 -0700 Subject: [PATCH 10/20] Add binding for aug_rename --- src/lib.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 6c7cfdb..b7f27d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -279,6 +279,14 @@ impl Augeas { self.make_result(()) } + pub fn rename(&mut self, src: &str, lbl: &str) -> Result<()> { + let src = CString::new(src)?; + let lbl = CString::new(lbl)?; + + unsafe { aug_rename(self.ptr, src.as_ptr(), lbl.as_ptr()) }; + self.make_result(()) + } + } impl Augeas { @@ -469,6 +477,19 @@ fn store_retrieve_test() { assert_eq!("parse error of kind parse_failed", format!("{}", err)); } +#[test] +fn rename_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + aug.rename("etc/passwd/root", "ruth").unwrap(); + + let ruth = aug.get("etc/passwd/ruth/uid").unwrap().unwrap(); + assert_eq!("0", ruth); + + let root = aug.get("etc/passwd/root/uid").unwrap(); + assert!(root.is_none()); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From 4fb765dc618721a0a37e765c400d9cada8e70443 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 18:51:46 -0700 Subject: [PATCH 11/20] Add binding for aug_transform --- src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b7f27d4..b70f7dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -287,6 +287,15 @@ impl Augeas { self.make_result(()) } + pub fn transform(&mut self, lens: &str, file: &str, + excl : bool) -> Result<()> { + let lens = CString::new(lens)?; + let file = CString::new(file)?; + + unsafe { aug_transform(self.ptr, lens.as_ptr(), + file.as_ptr(), excl as i32) }; + self.make_result(()) + } } impl Augeas { @@ -490,6 +499,15 @@ fn rename_test() { assert!(root.is_none()); } +#[test] +fn transform_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + aug.transform("Hosts.lns", "/usr/local/etc/hosts", false).unwrap(); + let p = aug.get("/augeas/load/Hosts/incl[. = '/usr/local/etc/hosts']").unwrap(); + assert!(p.is_some()); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From b2bbfb1e82bc69eb9cf3c57af4fdfe25e334fa18 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 20:04:43 -0700 Subject: [PATCH 12/20] util::ptr_to_string: no need to declare it unsafe --- src/lib.rs | 9 ++++----- src/util.rs | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b70f7dc..8281655 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,12 +91,11 @@ impl Augeas { let path = path.as_ptr(); let mut value: *const c_char = ptr::null_mut(); unsafe { aug_get(self.ptr, path, &mut value) }; - let value = unsafe { ptr_to_string(value) }; - self.make_result(value) + self.make_result(ptr_to_string(value)) } - pub fn label(&self, path: &str) -> Result> { + pub fn label(&self, path: &str) -> Result> { let path_c = CString::new(path)?; let mut return_value: *const c_char = ptr::null(); @@ -104,7 +103,7 @@ impl Augeas { aug_label(self.ptr, path_c.as_ptr(), &mut return_value) }; - self.make_result(unsafe { ptr_to_string(return_value) }) + self.make_result(ptr_to_string(return_value)) } pub fn matches(&self, path: &str) -> Result> { @@ -235,7 +234,7 @@ impl Augeas { } } - result.filename = unsafe { ptr_to_string(filename) }; + result.filename = ptr_to_string(filename); unsafe { libc::free(filename as *mut libc::c_void) }; Ok(Some(result)) } diff --git a/src/util.rs b/src/util.rs index 8b4c59b..713ea4f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,8 +1,7 @@ use std::ffi::CStr; use libc::c_char; -#[allow(unused_unsafe)] -pub unsafe fn ptr_to_string(s: *const c_char) -> Option { +pub fn ptr_to_string(s: *const c_char) -> Option { if s.is_null() { None } else { From 0905668eb8717ae53c8d2ebf0b7a853a7ae5687d Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 20:10:30 -0700 Subject: [PATCH 13/20] Add binding for aug_cp --- src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 8281655..9e27f1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -295,6 +295,14 @@ impl Augeas { file.as_ptr(), excl as i32) }; self.make_result(()) } + + pub fn cp(&mut self, src: &str, dst: &str) -> Result<()> { + let src = CString::new(src)?; + let dst = CString::new(dst)?; + + unsafe { aug_cp(self.ptr, src.as_ptr(), dst.as_ptr()) }; + self.make_result(()) + } } impl Augeas { @@ -507,6 +515,18 @@ fn transform_test() { assert!(p.is_some()); } +#[test] +fn cp_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + aug.cp("etc/passwd/root", "etc/passwd/ruth").unwrap(); + let ruth = aug.get("etc/passwd/ruth/uid").unwrap().unwrap(); + assert_eq!("0", ruth); + + let root = aug.get("etc/passwd/root/uid").unwrap().unwrap(); + assert_eq!("0", root); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From ee0bdc64d98ea10915ec2bf04aa012e8954a9eea Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 21:41:27 -0700 Subject: [PATCH 14/20] Add binding for aug_escape_name --- src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 9e27f1a..2ea06db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -303,6 +303,18 @@ impl Augeas { unsafe { aug_cp(self.ptr, src.as_ptr(), dst.as_ptr()) }; self.make_result(()) } + + pub fn escape_name(&self, inp: &str) -> Result> { + let inp = CString::new(inp)?; + let mut out : *mut c_char = ptr::null_mut(); + + unsafe { aug_escape_name(self.ptr, inp.as_ptr(), &mut out) }; + + let s = ptr_to_string(out); + unsafe { libc::free(out as *mut libc::c_void) }; + self.make_result(s) + } + } impl Augeas { @@ -527,6 +539,18 @@ fn cp_test() { assert_eq!("0", root); } +#[test] +fn escape_test() { + let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + // no escaping needed + let n = aug.escape_name("foo"); + assert_eq!(Ok(None), n); + + let n = aug.escape_name("foo["); + assert_eq!(Ok(Some(String::from("foo\\["))), n); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From 4efc37128ded483b843461eef863ee37051bd689 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 21:59:17 -0700 Subject: [PATCH 15/20] Add easy way to check for particular augeas error --- src/error.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/error.rs b/src/error.rs index 8285a3f..29bc253 100644 --- a/src/error.rs +++ b/src/error.rs @@ -68,6 +68,15 @@ impl fmt::Display for AugeasError { } } +impl Error { + pub fn is_code(&self, code : ErrorCode) -> bool { + match self { + Error::Augeas(err) => err.code == code, + _ => false + } + } +} + impl From for Error { fn from(err: NulError) -> Error { Error::Nul(err) From 6a6811e20470d99da3de1b99b8869bd5171576b1 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Thu, 25 Oct 2018 21:59:25 -0700 Subject: [PATCH 16/20] Add binding for aug_load_file --- src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2ea06db..764e526 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -315,6 +315,12 @@ impl Augeas { self.make_result(s) } + pub fn load_file(&mut self, file: &str) -> Result<()> { + let file = CString::new(file)?; + + unsafe { aug_load_file(self.ptr, file.as_ptr()) }; + self.make_result(()) + } } impl Augeas { @@ -551,6 +557,20 @@ fn escape_test() { assert_eq!(Ok(Some(String::from("foo\\["))), n); } +#[test] +fn load_file_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::NoLoad).unwrap(); + + aug.load_file("/etc/passwd").unwrap(); + let root = aug.get("etc/passwd/root/uid").unwrap(); + assert!(root.is_some()); + + let err = aug.load_file("/var/no/lens/for/this"); + assert!(err.is_err()); + let e = err.err().unwrap(); + assert!(e.is_code(ErrorCode::NoLens)); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From 2d3ada98e793de868ac6419e4b64a2a3c745c80d Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Fri, 26 Oct 2018 18:05:21 -0700 Subject: [PATCH 17/20] Add binding for aug_source --- src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 764e526..93931f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -321,6 +321,16 @@ impl Augeas { unsafe { aug_load_file(self.ptr, file.as_ptr()) }; self.make_result(()) } + + pub fn source(&self, path: &str) -> Result> { + let path = CString::new(path)?; + let mut file_path : *mut c_char = ptr::null_mut(); + + unsafe { aug_source(self.ptr, path.as_ptr(), &mut file_path) }; + let s = ptr_to_string(file_path); + unsafe { libc::free(file_path as *mut libc::c_void) }; + self.make_result(s) + } } impl Augeas { @@ -571,6 +581,16 @@ fn load_file_test() { assert!(e.is_code(ErrorCode::NoLens)); } +#[test] +fn source_test() { + let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + let s = aug.source("etc/passwd/root/uid").unwrap(); + // s should be Some("/files/etc/passwd") but Augeas versions before + // 1.11 had a bug that makes the result always None + assert!(s.is_none() || s.unwrap() == "/files/etc/passwd") +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From abf2af17bbe85f2f516e82e83e09c331f49a3145 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Fri, 26 Oct 2018 22:25:48 -0700 Subject: [PATCH 18/20] Add bindings for the aug_ns_* functions --- src/error.rs | 11 +++++ src/lib.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/src/error.rs b/src/error.rs index 29bc253..b3e7ae4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -148,6 +148,17 @@ impl Default for ErrorCode { fn default() -> ErrorCode { ErrorCode::NoError } } +impl From for Error { + fn from(code : ErrorCode) -> Error { + Error::Augeas(AugeasError { + code : code, + message : None, + minor_message : None, + details : None + }) + } +} + impl From for Error { fn from(kind: String) -> Error { Error::Parse(ParseError { diff --git a/src/lib.rs b/src/lib.rs index 93931f9..196cbc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,13 @@ impl Span { } } +#[derive(Debug)] +pub struct Attr { + pub label : Option, + pub value : Option, + pub file_path : Option +} + impl Augeas { pub fn init<'a>(root: impl Into>, loadpath: &str, flags: Flags) -> Result { let ref root = match root.into() { @@ -331,6 +338,85 @@ impl Augeas { unsafe { libc::free(file_path as *mut libc::c_void) }; self.make_result(s) } + + pub fn ns_attr(&self, var: &str, i: u32) -> Result { + let var = CString::new(var)?; + + let mut value : *const c_char = ptr::null_mut(); + let mut label : *const c_char = ptr::null_mut(); + let mut file_path : *mut c_char = ptr::null_mut(); + + let rc = unsafe { aug_ns_attr(self.ptr, var.as_ptr(), i as c_int, + &mut value, &mut label, &mut file_path) }; + if rc < 0 { + return self.make_error() + } + + let attr = Attr { + label: ptr_to_string(label), + value: ptr_to_string(value), + file_path: ptr_to_string(file_path) }; + + unsafe { libc::free(file_path as *mut libc::c_void) }; + + self.make_result(attr) + } + + pub fn ns_label(&self, var: &str, i: u32) -> Result { + let var = CString::new(var)?; + + let mut label : *const c_char = ptr::null_mut(); + + let rc = unsafe { aug_ns_label(self.ptr, var.as_ptr(), i as c_int, + &mut label, ptr::null_mut() ) }; + if rc < 0 { + return self.make_error() + } + + match ptr_to_string(label) { + Some(label) => Ok(label), + None => Err(Error::from(ErrorCode::NoMatch)) + } + } + + pub fn ns_index(&self, var: &str, i: u32) -> Result { + let var = CString::new(var)?; + + let mut index : c_int = 0; + + unsafe { aug_ns_label(self.ptr, var.as_ptr(), i as c_int, + ptr::null_mut(), &mut index ) }; + return self.make_result(index as u32) + } + + pub fn ns_value(&self, var: &str, i: u32) -> Result> { + let var = CString::new(var)?; + + let mut value : *const c_char = ptr::null_mut(); + unsafe { aug_ns_value(self.ptr, var.as_ptr(), i as c_int, + &mut value) }; + + self.make_result(ptr_to_string(value)) + } + + pub fn ns_count(&self, var: &str) -> Result { + let var = CString::new(var)?; + + let rc = unsafe { aug_ns_count(self.ptr, var.as_ptr()) }; + self.make_result(rc as u32) + } + + pub fn ns_path(&self, var: &str, i: u32) -> Result> { + let var = CString::new(var)?; + + let mut path : *mut c_char = ptr::null_mut(); + + unsafe { aug_ns_path(self.ptr, var.as_ptr(), i as c_int, &mut path) }; + let p = ptr_to_string(path); + unsafe { libc::free(path as *mut libc::c_void) }; + + self.make_result(p) + } } impl Augeas { @@ -591,6 +677,40 @@ fn source_test() { assert!(s.is_none() || s.unwrap() == "/files/etc/passwd") } +#[test] +fn ns_test() { + let mut aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); + + aug.defvar("x", "etc/passwd/*").unwrap(); + aug.defvar("uid", "etc/passwd/*/uid").unwrap(); + + let attr = aug.ns_attr("x", 0).unwrap(); + assert_eq!("root", attr.label.unwrap()); + assert!(attr.value.is_none()); + assert_eq!("/files/etc/passwd", attr.file_path.unwrap()); + + let attr = aug.ns_attr("x", 10000).unwrap_err(); + assert!(attr.is_code(ErrorCode::NoMatch)); + + let attr = aug.ns_attr("y", 0).unwrap_err(); + assert!(attr.is_code(ErrorCode::NoMatch)); + + let label = aug.ns_label("x", 0).unwrap(); + assert_eq!("root", label); + + let index = aug.ns_index("x", 4).unwrap(); + assert_eq!(0, index); + + let uid = aug.ns_value("uid", 2).unwrap().unwrap(); + assert_eq!("2", &uid); + + let count = aug.ns_count("uid").unwrap(); + assert_eq!(9, count); + + let path = aug.ns_path("uid", 0).unwrap().unwrap(); + assert_eq!("/files/etc/passwd/root/uid", path); +} + #[test] fn error_test() { let aug = Augeas::init("tests/test_root", "", Flags::None).unwrap(); From b62df2e9aef981d872252573538fdd641d658ba1 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Mon, 29 Oct 2018 18:17:25 -0700 Subject: [PATCH 19/20] Bump the version to something a little less alarming --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06001f9..7c51870 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,8 @@ name = "augeas" version = "0.1.0" -authors = ["panicbit "] -description = "High level bindings for augeas" +authors = ["panicbit ", "David Lutterkort "] +description = "Augeas bindings for Rust" license = "MIT" keywords = ["augeas", "bindings"] repository = "https://github.com/panicbit/rust-augeas" From c89f442f0f331e70698671784bd34f95ad4939f3 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Mon, 29 Oct 2018 18:17:34 -0700 Subject: [PATCH 20/20] Add some documentation --- src/lib.rs | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 196cbc3..bab7841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,40 @@ +//! Augeas bindings for Rust +//! +//! ## Usage +//! +//! In `Cargo.toml` +//! ```toml +//! [dependencies.augeas] +//! version = "0.1.0" +//! ``` +//! +//! ## Summary +//! +//! This crate implements Rust bindings to [augeas](http://augeas.net/), a library for reading, +//! modifying, and writing structured file, like configuration files. +//! +//! A typical interaction looks like this: +//! +//! ``` +//! extern crate augeas_sys; +//! extern crate augeas; +//! +//! use augeas_sys; +//! use augeas::{Augeas,Flags}; +//! +//! let mut aug = Augeas::init("/", "", Flags::None).unwrap(); +//! +//! let entry = aug.get("etc/hosts/*[canonical = 'host.example.com']/ip").unwrap(); +//! +//! if let Some(ip) = entry { +//! println!("The ip for host.example.com is {}", ip); +//! } else { +//! println!("There is no entry for host.example.com in /etc/hosts"); +//! } +//! +//! // Add an alias for host.example.com +//! aug.set("etc/hosts/*[canonical = 'host.example.com']/alias[last()+1]", "server.example.com"); +//! ``` extern crate augeas_sys; extern crate libc; #[macro_use] extern crate bitflags; @@ -23,10 +60,19 @@ use util::ptr_to_string; pub type Result = std::result::Result; +/// The handle for the Augeas library. +/// +/// The Augeas handle points to the in-memory data that Augeas manages, in +/// particular, the tree generated from parsing configuration files. pub struct Augeas { ptr: *mut augeas, } +/// The insert position. +/// +/// Use this enum with [`insert`](#method.insert) to indicate whether the +/// new node should be inserted before or after the node passed to +/// [`insert`](#method.insert) #[derive(Clone, Copy)] pub enum Position { Before, @@ -42,6 +88,16 @@ impl From for c_int { } } +/// A span in the tree +/// +/// The [`span`](#method.span) indicates where in a file a particular node +/// was found. The `label` and `value` give the character range from which +/// the node's label and value were read, and `span` gives the entire region +/// in the file from which the node was construted. If any of these values are +/// not available, e.g., because the node has no value, the corresponding range +/// will be empty. +/// +/// The `filename` provides the entire path to the file in the file system #[derive(Debug)] pub struct Span { pub label : Range, @@ -69,6 +125,18 @@ pub struct Attr { } impl Augeas { + /// Create a new Augeas handle. + /// + /// Use `root` as the filesystem root. If `root` is `None`, use the value + /// of the environment variable `AUGEAS_ROOT`, if it is set; otherwise, + /// use "/". + /// + /// The `loadpath` is a colon-separated list of directories that modules + /// should be searched in. This is in addition to the standard load path + /// and the directories listed in the environment variable AUGEAS_LENS_LIB + /// + /// The `flags` are a bitmask of values from `AugFlag`. + /// pub fn init<'a>(root: impl Into>, loadpath: &str, flags: Flags) -> Result { let ref root = match root.into() { Some(root) => Some(CString::new(root)?), @@ -93,6 +161,13 @@ impl Augeas { }) } + /// Lookup the value associated with `path`. + /// + /// Return `None` if there is no value associated with `path` or if no + /// node matches `path`, and `Some(value)` if there is exactly one node + /// with a value. + /// + /// It is an error if `path` matches more than one node. pub fn get(&self, path: &str) -> Result> { let ref path = CString::new(path)?; let path = path.as_ptr(); @@ -102,6 +177,13 @@ impl Augeas { self.make_result(ptr_to_string(value)) } + /// Lookup the label associated with `path`. + /// + /// Return `Some(label)` if `path` matches a node that has a label, and + /// `None` if `path` matches no node, or matches exactly one node + /// without a label. + /// + /// It is an error if `path` matches more than one node. pub fn label(&self, path: &str) -> Result> { let path_c = CString::new(path)?; let mut return_value: *const c_char = ptr::null(); @@ -113,6 +195,11 @@ impl Augeas { self.make_result(ptr_to_string(return_value)) } + /// Find all nodes matching `path` + /// + /// Find all nodes matching the path expression `path` and return their + /// paths in an unambiguous form that can be used with + /// [`get`](#method.get) to get their value. pub fn matches(&self, path: &str) -> Result> { let c_path = CString::new(path)?; @@ -137,6 +224,10 @@ impl Augeas { } } + /// Count the nodes matching `path` + /// + /// Find all nodes matching the path expression `path` and return how + /// many there are. pub fn count(&self, path: &str) -> Result { let path = CString::new(path)?; @@ -145,11 +236,23 @@ impl Augeas { self.make_result(r as u32) } + /// Save all pending changes to disk + /// + /// Turn all files in the tree for which entries have been changed, + /// added, or deleted back into text and write them back to file. pub fn save(&mut self) -> Result<()> { unsafe { aug_save(self.ptr) }; self.make_result(()) } + /// Set the value of a node. + /// + /// Find the node matching `path` and change its value. If there is no + /// such node, an attempt is made to create one, though that might not + /// be possible depending on the complexity of the path expression + /// `path`. + /// + /// It is an error if more than one node matches `path` pub fn set(&mut self, path: &str, value: &str) -> Result<()> { let path_c = CString::new(path.as_bytes())?; let value_c = CString::new(value.as_bytes())?; @@ -158,6 +261,13 @@ impl Augeas { self.make_result(()) } + /// Insert a new node: find the node matching `path` and create a new + /// sibling for it with the given `label`. The sibling is created + /// before or after the node `path`, depending on the value of `pos`. + /// + /// It is an error if `path` matches no nodes, or more than one + /// node. The `label` must not contain a `/`, `*` or end with a + /// bracketed index `[N]`. pub fn insert(&mut self, path: &str, label: &str, pos:Position) -> Result<()> { let path = CString::new(path.as_bytes())?; let label = CString::new(label.as_bytes())?; @@ -166,6 +276,8 @@ impl Augeas { self.make_result(()) } + /// Remove `path` and all its children and return the number of nodes + /// removed. pub fn rm(&mut self, path: &str) -> Result { let path = CString::new(path.as_bytes())?; let r = unsafe { @@ -177,6 +289,7 @@ impl Augeas { self.make_result(r as u32) } + /// Move the node matching `src` to `dst`. pub fn mv(&mut self, src: &str, dst: &str) -> Result<()> { let src = CString::new(src)?; let dst = CString::new(dst)?; @@ -185,6 +298,13 @@ impl Augeas { self.make_result(()) } + /// Define a variable `name` whose value is the result of evaluating + /// `expr`. If a variable `name` already exists, its name will be + /// replaced with the result of evaluating `expr`. Context will not be + /// applied to `expr`. + /// + /// Path variables can be used in path expressions later on by prefixing + /// them with '$'. pub fn defvar(&mut self, name: &str, expr: &str) -> Result<()> { let name = CString::new(name)?; let expr = CString::new(expr)?; @@ -193,6 +313,27 @@ impl Augeas { self.make_result(()) } + /// Remove the variable `name`. + /// + /// It is not an error if the variable does not exist. + pub fn rmvar(&mut self, name: &str) -> Result<()> { + let name = CString::new(name)?; + + unsafe { aug_defvar(self.ptr, name.as_ptr(), ptr::null_mut()) }; + self.make_result(()) + } + + /// Define a variable `name` whose value is the result of evaluating `expr`, + /// which must be non-NULL and evaluate to a nodeset. If a variable `name` + /// already exists, its name will be replaced with the result of evaluating + /// `expr`. + /// + /// If `expr` evaluates to an empty nodeset, a node is created, + /// equivalent to calling [`set(expr, value)`](#method.set) and `name` + /// will be the nodeset containing that single node. + /// + /// If a node was created, the method returns `true`, and `false` if no + /// node was created. pub fn defnode(&mut self, name: &str, expr: &str, value: &str) -> Result { let name = CString::new(name)?; let expr = CString::new(expr)?; @@ -204,6 +345,9 @@ impl Augeas { self.make_result(cr == 1) } + /// Load the tree from file. If a tree was already loaded, reload the + /// tree, erasing any changes that may have been made since the last + /// `load` or `save`. pub fn load(&mut self) -> Result<()> { unsafe { aug_load(self.ptr) }; self.make_result(())