From 4fbd00cc3812f45b5041817fc07507151e9d6cb5 Mon Sep 17 00:00:00 2001 From: Fabian Freyer Date: Fri, 28 Dec 2018 03:18:34 +0100 Subject: [PATCH] python: PyO3 0.5 --- bindings/python/Cargo.toml | 2 +- bindings/python/src/lib.rs | 63 ++++--- bindings/python/src/oldlib.rs | 320 ++++++++++++++++++++++++++++++++++ 3 files changed, 356 insertions(+), 29 deletions(-) create mode 100644 bindings/python/src/oldlib.rs diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index d538e3e78..7c13372fc 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -17,5 +17,5 @@ rctl = "^0" path = "../../" [dependencies.pyo3] -version = "0.2" +version = "0.5" features = ["extension-module"] diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 8ffbd35cc..1ad99e36e 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro, proc_macro_path_invoc, specialization, const_fn)] +#![feature(specialization, const_fn)] extern crate jail; extern crate pyo3; extern crate rctl; @@ -6,24 +6,26 @@ extern crate rctl; use std::collections::HashMap; use pyo3::prelude::*; -use pyo3::py::{class, methods, modinit}; +use pyo3::types::{PyDict, PyInt, PyString}; +use pyo3::PyObjectWithToken; +use pyo3::{exceptions, PyDowncastError}; use jail as native; -#[class] +#[pyclass] struct JailError { inner: native::JailError, token: PyToken, } -#[methods] +#[pymethods] impl JailError { fn __repr__(&self) -> PyResult { Ok(format!("{}", self.inner)) } } -#[class] +#[pyclass] struct RunningJail { inner: native::RunningJail, // As Python keeps its own reference counter, and we can't be sure that @@ -33,7 +35,7 @@ struct RunningJail { token: PyToken, } -#[methods] +#[pymethods] impl RunningJail { #[new] fn __new__(obj: &PyRawObject, jid: i32) -> PyResult<()> { @@ -60,7 +62,7 @@ impl RunningJail { Ok(self .inner .ips() - .map_err(|_| exc::SystemError::new("Could not get IP Addresses"))? + .map_err(|_| exceptions::SystemError::py_err("Could not get IP Addresses"))? .iter() .map(|addr| format!("{}", addr)) .collect()) @@ -73,7 +75,7 @@ impl RunningJail { Ok(self .inner .params() - .map_err(|_| exc::SystemError::new("Could not get parameters"))? + .map_err(|_| exceptions::SystemError::py_err("Could not get parameters"))? .iter() .filter_map(|(key, value)| { let object = match value { @@ -105,7 +107,7 @@ impl RunningJail { /// resource limits, etc. fn stop(&mut self) -> PyResult> { if self.dead { - return Err(exc::ValueError::new( + return Err(exceptions::ValueError::py_err( "The RunningJail instance is no longer live", )); } @@ -114,7 +116,7 @@ impl RunningJail { .inner .clone() .stop() - .map_err(|_| exc::SystemError::new("Jail stop failed"))?; + .map_err(|_| exceptions::SystemError::py_err("Jail stop failed"))?; self.dead = true; self.py().init(|token| StoppedJail { inner, token }) } @@ -122,7 +124,7 @@ impl RunningJail { /// Kill the Jail. fn kill(&mut self) -> PyResult<()> { if self.dead { - return Err(exc::ValueError::new( + return Err(exceptions::ValueError::py_err( "The RunningJail instance is no longer live", )); } @@ -130,7 +132,7 @@ impl RunningJail { self.inner .clone() .kill() - .map_err(|_| exc::SystemError::new("Jail stop failed"))?; + .map_err(|_| exceptions::SystemError::py_err("Jail stop failed"))?; self.dead = true; Ok(()) } @@ -139,7 +141,7 @@ impl RunningJail { #[getter] fn get_racct_usage(&self) -> PyResult> { if self.dead { - return Err(exc::ValueError::new( + return Err(exceptions::ValueError::py_err( "The RunningJail instance is no longer live", )); } @@ -147,22 +149,25 @@ impl RunningJail { let usage = self.inner.racct_statistics(); let usage_map = usage.map_err(|e| match e { native::JailError::RctlError(rctl::Error::InvalidKernelState(s)) => match s { - rctl::State::Disabled => exc::SystemError::new( + rctl::State::Disabled => exceptions::SystemError::py_err( "Resource accounting is disabled. To enable resource \ accounting, set the `kern.racct.enable` tunable to 1.", ), - rctl::State::NotPresent => exc::SystemError::new( + rctl::State::NotPresent => exceptions::SystemError::py_err( "Resource accounting is not enabled in the kernel. \ This feature requires the kernel to be compiled with \ `OPTION RACCT` set. Current GENERIC kernels should \ have this option set.", ), - rctl::State::Enabled => exc::SystemError::new( + rctl::State::Enabled => exceptions::SystemError::py_err( "rctl::Error::InvalidKernelState returned but state \ is enabled. This really shouldn't happen.", ), + rctl::State::Jailed => exceptions::SystemError::py_err( + "Resource accounting isn't available in a jail.", + ), }, - _ => exc::SystemError::new("Could not get RACCT accounting information"), + _ => exceptions::SystemError::py_err("Could not get RACCT accounting information"), })?; Ok(usage_map @@ -172,17 +177,19 @@ impl RunningJail { } fn attach(&self) -> PyResult<()> { - self.attach() - .map_err(|_| exc::SystemError::new("jail_attach failed")) + self.inner + .attach() + .map_err(|_| exceptions::SystemError::py_err("jail_attach failed")) } fn defer_cleanup(&self) -> PyResult<()> { - self.defer_cleanup() - .map_err(|_| exc::SystemError::new("Could not clear persist flag")) + self.inner + .defer_cleanup() + .map_err(|_| exceptions::SystemError::py_err("Could not clear persist flag")) } } -#[class] +#[pyclass] struct StoppedJail { inner: native::StoppedJail, token: PyToken, @@ -194,7 +201,7 @@ fn parameter_hashmap(dict: &PyDict) -> PyResult = key .try_into() - .map_err(|_| exc::TypeError::new("Parameter key must be a string")); + .map_err(|_| exceptions::TypeError::py_err("Parameter key must be a string")); let key: PyResult = key.and_then(|k| k.extract()); let py_string: Result<&PyString, PyDowncastError> = value.try_into(); @@ -205,7 +212,7 @@ fn parameter_hashmap(dict: &PyDict) -> PyResult PyResult PyResult<()> { +#[pymodinit] +fn _jail(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/bindings/python/src/oldlib.rs b/bindings/python/src/oldlib.rs new file mode 100644 index 000000000..79cdf386c --- /dev/null +++ b/bindings/python/src/oldlib.rs @@ -0,0 +1,320 @@ +#![feature(proc_macro, proc_macro_path_invoc, specialization, const_fn)] +extern crate jail; +extern crate pyo3; +extern crate rctl; + +use std::collections::HashMap; + +use pyo3::prelude::*; +use pyo3::exceptions; +use pyo3::PyErr; +use pyo3::types::{PyString,PyDict}; + +use jail as native; + +// Exceptions +impl std::convert::From for PyErr { + fn from(err: JailError) -> PyErr { + exceptions::OSError.into() + } +} + +#[pyclass] +struct JailError { + inner: native::JailError, + token: PyToken, +} + +#[pymethods] +impl JailError { + fn __repr__(&self) -> PyResult { + Ok(format!("{}", self.inner)) + } +} + +#[pyclass] +struct RunningJail { + inner: native::RunningJail, + // As Python keeps its own reference counter, and we can't be sure that + // that reference will be destroyed when we stop or kill the Jail, we have + // to keep track of that ourselves. + dead: bool, + token: PyToken, +} + +#[pymethods] +impl RunningJail { + #[new] + fn __new__(obj: &PyRawObject, jid: i32) -> PyResult<()> { + obj.init(|token| RunningJail { + inner: native::RunningJail::from_jid(jid), + dead: false, + token, + }) + } + + /// Return a String representation of the Jail + fn __repr__(&self) -> PyResult { + Ok(format!("{:?}", self.inner)) + } + + #[getter] + /// The Jail ID + fn get_jid(&self) -> PyResult { + Ok(self.inner.jid) + } + + #[getter] + fn get_ips(&self) -> PyResult> { + Ok(self + .inner + .ips() + .map_err(|_| exceptions::SystemError::new("Could not get IP Addresses"))? + .iter() + .map(|addr| format!("{}", addr)) + .collect()) + } + + #[getter] + fn get_parameters(&self) -> PyResult> { + println!("parameter getter"); + + Ok(self + .inner + .params() + .map_err(|_| exceptions::SystemError::new("Could not get parameters"))? + .iter() + .filter_map(|(key, value)| { + let object = match value { + native::param::Value::Int(i) => Some(i.into_object(self.py())), + native::param::Value::String(s) => Some(s.into_object(self.py())), + native::param::Value::Ipv4Addrs(addrs) => Some( + addrs + .iter() + .map(|addr| format!("{}", addr)) + .collect::>() + .into_object(self.py()), + ), + native::param::Value::Ipv6Addrs(addrs) => Some( + addrs + .iter() + .map(|addr| format!("{}", addr)) + .collect::>() + .into_object(self.py()), + ), + _ => None, + }; + + object.map(|x| (key.clone(), x.into_object(self.py()))) + }) + .collect()) + } + + /// Stop the Jail, returning a StoppedJail instance with all properties, + /// resource limits, etc. + fn stop(&mut self) -> PyResult> { + if self.dead { + return Err(exceptions::ValueError::new( + "The RunningJail instance is no longer live", + )); + } + + let inner = self + .inner + .clone() + .stop() + .map_err(|_| exceptions::SystemError::new("Jail stop failed"))?; + self.dead = true; + self.py().init(|token| StoppedJail { inner, token }) + } + + /// Kill the Jail. + fn kill(&mut self) -> PyResult<()> { + if self.dead { + return Err(exceptions::ValueError::new( + "The RunningJail instance is no longer live", + )); + } + + self.inner + .clone() + .kill() + .map_err(|_| exceptions::SystemError::new("Jail stop failed"))?; + self.dead = true; + Ok(()) + } + + /// Get RACCT resource accounting information + #[getter] + fn get_racct_usage(&self) -> PyResult> { + if self.dead { + return Err(exceptions::ValueError::new( + "The RunningJail instance is no longer live", + )); + } + + let usage = self.inner.racct_statistics(); + let usage_map = usage.map_err(|e| match e { + native::JailError::RctlError(rctl::Error::InvalidKernelState(s)) => match s { + rctl::State::Disabled => exceptions::SystemError::new( + "Resource accounting is disabled. To enable resource \ + accounting, set the `kern.racct.enable` tunable to 1.", + ), + rctl::State::NotPresent => exceptions::SystemError::new( + "Resource accounting is not enabled in the kernel. \ + This feature requires the kernel to be compiled with \ + `OPTION RACCT` set. Current GENERIC kernels should \ + have this option set.", + ), + rctl::State::Enabled => exceptions::SystemError::new( + "rctl::Error::InvalidKernelState returned but state \ + is enabled. This really shouldn't happen.", + ), + }, + _ => exceptions::SystemError::new("Could not get RACCT accounting information"), + })?; + + Ok(usage_map + .iter() + .map(|(resource, metric)| (format!("{}", resource), *metric)) + .collect()) + } + + fn attach(&self) -> PyResult<()> { + self.attach() + .map_err(|_| exceptions::SystemError::new("jail_attach failed")) + } + + fn defer_cleanup(&self) -> PyResult<()> { + self.defer_cleanup() + .map_err(|_| exceptions::SystemError::new("Could not clear persist flag")) + } +} + +#[pyclass] +struct StoppedJail { + inner: native::StoppedJail, + token: PyToken, +} + +fn parameter_hashmap(dict: &PyDict) -> PyResult> { + let (converted, failed): (Vec<_>, Vec<_>) = dict + .iter() + .map(|(key, value)| { + let key: PyResult<&PyString> = key + .try_into() + .map_err(|_| exceptions::TypeError::new("Parameter key must be a string")); + let key: PyResult = key.and_then(|k| k.extract()); + + let py_string: Result<&PyString, PyDowncastError> = value.try_into(); + let py_num: Result<&PyInt, PyDowncastError> = value.try_into(); + + let wrapped_value = if let Ok(string) = py_string { + string.extract().map(native::param::Value::String) + } else if let Ok(num) = py_num { + num.extract().map(native::param::Value::Int) + } else { + Err(exceptions::TypeError::new( + "Only string and integer parameters are supported", + )) + }; + + (key, wrapped_value) + }) + .map(|t| match t { + (Ok(key), Ok(value)) => Ok((key, value)), + (Err(e), _) => Err(e), + (_, Err(e)) => Err(e), + }) + .partition(Result::is_ok); + + for e in failed { + return Err(e.unwrap_err()); + } + + Ok(converted.into_iter().map(Result::unwrap).collect()) +} + +#[pymethods] +impl StoppedJail { + #[new] + fn __new__( + obj: &PyRawObject, + path: String, + name: Option, + parameters: Option<&PyDict>, + ) -> PyResult<()> { + let mut inner = native::StoppedJail::new(path); + inner.name = name; + + if let Some(params) = parameters { + inner.params = parameter_hashmap(params)?; + } + + obj.init(|token| StoppedJail { inner, token }) + } + + fn __repr__(&self) -> PyResult { + Ok(format!("{:?}", self.inner)) + } + + #[getter] + fn get_parameters(&self) -> PyResult> { + Ok(self + .inner + .params + .iter() + .filter_map(|(key, value)| { + let object = match value { + native::param::Value::Int(i) => Some(i.into_object(self.py())), + native::param::Value::String(s) => Some(s.into_object(self.py())), + native::param::Value::Ipv4Addrs(addrs) => Some( + addrs + .iter() + .map(|addr| format!("{}", addr)) + .collect::>() + .into_object(self.py()), + ), + native::param::Value::Ipv6Addrs(addrs) => Some( + addrs + .iter() + .map(|addr| format!("{}", addr)) + .collect::>() + .into_object(self.py()), + ), + _ => None, + }; + + object.map(|x| (key.clone(), x.into_object(self.py()))) + }) + .collect()) + } + + #[setter] + fn set_parameters(&mut self, dict: &PyDict) -> PyResult<()> { + self.inner.params = parameter_hashmap(dict)?; + Ok(()) + } + + fn start(&self) -> PyResult> { + let inner = self + .inner + .clone() + .start() + .map_err(|_| exceptions::SystemError::new("Jail start failed"))?; + self.py().init(|token| RunningJail { + inner, + dead: false, + token, + }) + } +} + +#[pymodinit] +fn init_mod(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + Ok(()) +}