diff --git a/Cargo.toml b/Cargo.toml index 8f29f8b1..c406d686 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ prost-types = { version = "0.6.1" } rrg-proto = { path = "proto/" } simplelog = { version = "0.7.6" } structopt = { version = "0.3.12" } +sys-info = { version = "0.7.0" } +libc = { version = "0.2" } flate2 = { version = "1.0.14" } byteorder = { version = "1.3.4" } sysinfo = { version = "0.14.1" } @@ -32,3 +34,4 @@ proc-mounts = { version = "0.2.4" } [target.'cfg(target_os = "linux")'.dev-dependencies] fuse = { version = "0.3.1" } users = { version = "0.10.0" } + diff --git a/src/action/mod.rs b/src/action/mod.rs index 7d3476a4..aeec9775 100644 --- a/src/action/mod.rs +++ b/src/action/mod.rs @@ -15,6 +15,7 @@ //! instance of the corresponding request type and send some (zero or more) //! instances of the corresponding response type. +pub mod platform; #[cfg(target_os = "linux")] pub mod filesystems; @@ -100,6 +101,7 @@ where match action { "SendStartupInfo" => task.execute(self::startup::handle), "GetClientInfo" => task.execute(self::metadata::handle), + "GetPlatformInfo" => task.execute(self::platform::handle), "ListNetworkConnections" => task.execute(self::network::handle), #[cfg(target_family = "unix")] diff --git a/src/action/platform.rs b/src/action/platform.rs new file mode 100644 index 00000000..b9d8c775 --- /dev/null +++ b/src/action/platform.rs @@ -0,0 +1,262 @@ +// Copyright 2020 Google LLC +// +// Use of this source code is governed by an MIT-style license that can be found +// in the LICENSE file or at https://opensource.org/licenses/MIT. + +use sys_info::{linux_os_release, os_type, hostname}; +use libc::c_char; +use crate::session::{self, Session}; +use std::ffi::CStr; +use std::option::Option; +use std::fmt::{Display, Formatter}; + +use rrg_proto::Uname; + +#[derive(Debug)] +enum Error { + CannotGetOSType(sys_info::Error), + CannotGetLinuxRelease(sys_info::Error) +} + +impl std::error::Error for Error { + + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use Error::*; + + match *self { + CannotGetOSType(ref error) => Some(error), + CannotGetLinuxRelease(ref error) => Some(error) + } + } +} + +impl Display for Error { + + fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result { + use Error::*; + + match *self { + CannotGetOSType(ref error) => { + write!(fmt, "can't get OS type error: {}", error) + }, + CannotGetLinuxRelease(ref error) => { + write!(fmt, "can't get Linux release info error: {}", error) + } + } + } +} + +impl From for session::Error { + + fn from(error: Error) -> session::Error { + session::Error::action(error) + } +} + +/// A response type for `GetPlatformInfo` action. +/// Gives client platform information. +#[derive(Default)] +pub struct Response { + + system: Option, + release_name: Option, + version_id: Option, + machine: Option, + kernel_release: Option, + fqdn: Option, + architecture: Option, + node: Option +} + +/// Function that converts raw C-strings into `String` type +fn convert_raw_string(c_string: &[c_char]) -> String { + unsafe { + String::from(CStr::from_ptr(c_string.as_ptr()).to_string_lossy().into_owned()) + } +} + +#[cfg(target_os = "linux")] +use libc::{uname, utsname}; + +/// Function that returns `Response` for Linux operating systems +fn get_linux_response(session: &mut S, os_type: String) -> session::Result<()> { + #[cfg(target_os = "linux")] + { + let linux_release_info = linux_os_release() + .map_err(Error::CannotGetLinuxRelease)?; + + let mut system_info: utsname; + + unsafe { + system_info = std::mem::zeroed(); + uname(&mut system_info); + } + + session.reply(Response { + system: Some(os_type), + release_name: linux_release_info.name, + version_id: linux_release_info.version_id, + machine: Some(convert_raw_string(&system_info.machine)), + kernel_release: Some(convert_raw_string(&system_info.release)), + fqdn: hostname().ok(), + architecture: Some(convert_raw_string(&system_info.machine)), + node: Some(convert_raw_string(&system_info.nodename)) + })?; + } + Ok(()) +} + +/// Handles requests for `GetPlatformInfo` action. +pub fn handle(session:&mut S, _: ()) -> session::Result<()> { + let os_type = os_type().map_err(Error::CannotGetOSType)?; + if cfg!(target_os = "linux") { + get_linux_response(session, os_type)?; + } else { + session.reply(Response { + system: Some(os_type), + fqdn: hostname().ok(), + ..Default::default() // TODO: Add other systems + })?; + } + + Ok(()) +} + +impl super::Response for Response { + + const RDF_NAME: Option<&'static str> = Some("Uname"); + + type Proto = rrg_proto::Uname; + + /// Convert `Response` struct to protobuf message `Uname`. + fn into_proto(self) -> rrg_proto::Uname { + let system = self.system.unwrap_or(String::from("None")); + let release_name = self.release_name.unwrap_or(String::from("None")); + let architecture = self.architecture.unwrap_or(String::from("None")); + let pep425tag = Some( + format!("{}_{}_{}", + system, + release_name, + architecture + )); + Uname { + system: Some(system), + release: Some(release_name), + version: self.version_id, + machine: self.machine, + kernel: self.kernel_release, + fqdn: self.fqdn, + architecture: Some(architecture), + node: self.node, + pep425tag: pep425tag, + ..Default::default() + } + } +} + +#[cfg(test)] +mod test { + + use super::*; + + // #[test] + // fn test_system() { + // let mut session = session::test::Fake::new(); + // assert!(handle(&mut session, ()).is_ok()); + + // assert_eq!(session.reply_count(), 1); + // let platform_info = &session.reply::(0); + + // assert!(platform_info.system.is_some()); + // } + + #[test] + #[cfg(target_os = "linux")] + fn test_system_linux() { + let mut session = session::test::Fake::new(); + assert!(handle(&mut session, ()).is_ok()); + + assert_eq!(session.reply_count(), 1); + let platform_info = &session.reply::(0); + + assert_eq!(platform_info.system.as_ref().unwrap(), "Linux"); + } + + #[test] + #[cfg(target_os = "windows")] + fn test_system_windows() { + let mut session = session::test::Fake::new(); + assert!(handle(&mut session, ()).is_ok()); + + assert_eq!(session.reply_count(), 1); + let platform_info = &session.reply::(0); + + assert_eq!(platform_info.system.as_ref().unwrap(), "Windows"); + } + + #[test] + #[cfg(target_os = "linux")] + fn test_linux_release() { + let mut session = session::test::Fake::new(); + assert!(handle(&mut session, ()).is_ok()); + + assert_eq!(session.reply_count(), 1); + let platform_info = &session.reply::(0); + assert!(platform_info.release_name.is_some()); + } + + #[test] + #[cfg(target_os = "linux")] + fn test_linux_version() { + let mut session = session::test::Fake::new(); + assert!(handle(&mut session, ()).is_ok()); + + assert_eq!(session.reply_count(), 1); + let platform_info = &session.reply::(0); + assert!(platform_info.version_id.is_some()); + } + + #[test] + #[cfg(target_os = "linux")] + fn test_linux_machine() { + let mut session = session::test::Fake::new(); + assert!(handle(&mut session, ()).is_ok()); + + assert_eq!(session.reply_count(), 1); + let platform_info = &session.reply::(0); + assert!(platform_info.machine.is_some()); + } + + #[test] + #[cfg(target_os = "linux")] + fn test_linux_kernel_release() { + let mut session = session::test::Fake::new(); + assert!(handle(&mut session, ()).is_ok()); + + assert_eq!(session.reply_count(), 1); + let platform_info = &session.reply::(0); + assert!(platform_info.kernel_release.is_some()); + } + + #[test] + #[cfg(target_os = "linux")] + fn test_linux_architecture() { + let mut session = session::test::Fake::new(); + assert!(handle(&mut session, ()).is_ok()); + + assert_eq!(session.reply_count(), 1); + let platform_info = &session.reply::(0); + assert!(platform_info.architecture.is_some()); + } + + #[test] + #[cfg(target_os = "linux")] + fn test_linux_node() { + let mut session = session::test::Fake::new(); + assert!(handle(&mut session, ()).is_ok()); + + assert_eq!(session.reply_count(), 1); + let platform_info = &session.reply::(0); + assert!(platform_info.node.is_some()); + } +}