Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move system information gathering to common crate #342

Merged
merged 12 commits into from
Jan 18, 2022
16 changes: 9 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion aziotctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ foreign-types-shared = "0.1"
hyper = "0.14"
hyper-openssl = "0.9"
libc = "0.2"
nix = "0.18"
nix = "0.22"
onalante-msft marked this conversation as resolved.
Show resolved Hide resolved
log = "0.4"
openssl = "0.10"
serde = { version = "1", features = ["derive"] }
Expand Down
5 changes: 3 additions & 2 deletions aziotctl/aziotctl-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ edition = "2021"
anyhow = "1.0.34"
base64 = "0.13"
log = "0.4"
nix = "0.18"
nix = "0.22"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.59"
serde_json = "1"
serde_with = "1"
url = { version = "2", features = ["serde"] }

aziot-certd-config = { path = "../../cert/aziot-certd-config" }
Expand Down
145 changes: 145 additions & 0 deletions aziotctl/aziotctl-common/src/host_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) Microsoft. All rights reserved.

use std::env::consts::ARCH;
use std::fs;
use std::io::{self, BufRead};

use serde::Serialize;
use serde_with::skip_serializing_none;

#[cfg(target_pointer_width = "32")]
const BITNESS: usize = 32;
#[cfg(target_pointer_width = "64")]
const BITNESS: usize = 64;
#[cfg(target_pointer_width = "128")]
const BITNESS: usize = 128;

/// A subset of the DMI variables exposed through /sys/devices/virtual/dmi/id.
///
/// Examples:
///
/// ```ignore
/// Host | name | version | vendor
/// ---------+-----------------+---------+-----------------------
/// Hyper-V | Virtual Machine | 7.0 | Microsoft Corporation
/// ```
///
/// Ref: <https://www.kernel.org/doc/html/latest/filesystems/sysfs.html>
#[derive(Clone, Debug, Serialize)]
pub struct DmiInfo {
pub board: Option<String>,
pub family: Option<String>,
pub product: Option<String>,
pub sku: Option<String>,
pub version: Option<String>,
pub vendor: Option<String>,
}

impl Default for DmiInfo {
fn default() -> Self {
Self {
board: try_read_dmi("board_name"),
family: try_read_dmi("product_family"),
product: try_read_dmi("product_name"),
sku: try_read_dmi("product_sku"),
version: try_read_dmi("product_version"),
vendor: try_read_dmi("sys_vendor"),
}
}
}

/// A subset of the fields from /etc/os-release.
///
/// Examples:
///
/// ```ignore
/// OS | id | version_id
/// ---------------------+---------------------+------------
/// CentOS 7 | centos | 7
/// Debian 9 | debian | 9
/// openSUSE Tumbleweed | opensuse-tumbleweed | 20190325
/// Ubuntu 18.04 | ubuntu | 18.04
/// ```
///
/// Ref: <https://www.freedesktop.org/software/systemd/man/os-release.html>
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize)]
pub struct OsInfo {
pub id: Option<String>,
pub version_id: Option<String>,
pub pretty_name: Option<String>,
pub arch: &'static str,
pub bitness: usize,
}

impl Default for OsInfo {
fn default() -> Self {
let mut result = Self {
id: None,
version_id: None,
pretty_name: None,
arch: ARCH,
bitness: BITNESS,
};

let os_release = fs::File::open("/etc/os-release")
.or_else(|_| fs::File::open("/usr/lib/os-release"));

if let Ok(os_release) = os_release {
let os_release = io::BufReader::new(os_release);

for line in os_release.lines() {
if let Ok(line) = &line {
match parse_shell_line(line) {
Some(("ID", value)) => {
result.id = Some(value.to_owned());
},
Some(("VERSION_ID", value)) => {
result.version_id = Some(value.to_owned());
},
Some(("PRETTY_NAME", value)) => {
result.pretty_name = Some(value.to_owned());
},
_ => (),
};
}
else {
break;
}
}
}

result
}
}

pub fn parse_shell_line(line: &str) -> Option<(&str, &str)> {
let line = line.trim();

let (key, value) = line.split_once('=')?;

// The value is essentially a shell string, so it can be quoted in single or
// double quotes, and can have escaped sequences using backslash.
// For simplicitly, just trim the quotes instead of implementing a full shell
// string grammar.
let value = if (value.starts_with('\'') && value.ends_with('\''))
gordonwang0 marked this conversation as resolved.
Show resolved Hide resolved
|| (value.starts_with('"') && value.ends_with('"'))
{
&value[1..(value.len() - 1)]
} else {
value
};

Some((key, value))
}

fn try_read_dmi(entry: &'static str) -> Option<String> {
let path = format!("/sys/devices/virtual/dmi/id/{}", entry);

let bytes = fs::read(path).ok()?;

Some(String::from_utf8(bytes)
onalante-msft marked this conversation as resolved.
Show resolved Hide resolved
.ok()?
.trim()
.to_owned())
}
1 change: 1 addition & 0 deletions aziotctl/aziotctl-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use serde::{Deserialize, Serialize};

pub mod check_last_modified;
pub mod config;
pub mod host_info;
pub mod system;

#[derive(Debug, Serialize, Deserialize)]
Expand Down
91 changes: 5 additions & 86 deletions aziotctl/src/internal/check/additional_info.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// Copyright (c) Microsoft. All rights reserved.

use std::env::consts::ARCH;
use std::str;

use byte_unit::{Byte, ByteUnit};
use serde::Serialize;
use sysinfo::{DiskExt, SystemExt};

use aziotctl_common::host_info::{DmiInfo, OsInfo};

/// Additional info for the JSON output of `aziotctl check`
#[derive(Clone, Debug, Serialize)]
pub struct AdditionalInfo {
// TODO: update https://github.com/Azure/azure-iotedge to include aziotd version
now: chrono::DateTime<chrono::Utc>,
os: OsInfo,
dmi: DmiInfo,
system_info: SystemInfo,

#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -26,7 +26,8 @@ impl AdditionalInfo {
pub fn new(iothub_hostname: Option<String>, local_gateway_hostname: Option<String>) -> Self {
AdditionalInfo {
now: chrono::Utc::now(),
os: OsInfo::new(),
os: OsInfo::default(),
dmi: DmiInfo::default(),
system_info: SystemInfo::new(),

iothub_hostname,
Expand All @@ -35,88 +36,6 @@ impl AdditionalInfo {
}
}

/// A subset of the fields from /etc/os-release.
///
/// Examples:
///
/// ```ignore
/// OS | id | version_id
/// ---------------------+---------------------+------------
/// CentOS 7 | centos | 7
/// Debian 9 | debian | 9
/// openSUSE Tumbleweed | opensuse-tumbleweed | 20190325
/// Ubuntu 18.04 | ubuntu | 18.04
/// ```
///
/// Ref: <https://www.freedesktop.org/software/systemd/man/os-release.html>
#[derive(Clone, Debug, Serialize)]
pub struct OsInfo {
id: Option<String>,
version_id: Option<String>,
arch: &'static str,
bitness: usize,
}

impl OsInfo {
pub fn new() -> Self {
use std::fs::File;
use std::io::{BufRead, BufReader};

let mut result = OsInfo {
id: None,
version_id: None,
arch: ARCH,
// Technically wrong if someone runs an arm32 build on arm64,
// but we have dedicated arm64 builds so hopefully they don't.
bitness: std::mem::size_of::<usize>() * 8,
};

if let Ok(os_release) = File::open("/etc/os-release") {
let mut os_release = BufReader::new(os_release);

let mut line = String::new();
loop {
match os_release.read_line(&mut line) {
Ok(0) | Err(_) => break,
Ok(_) => {
if let Some((key, value)) = parse_os_release_line(&line) {
if key == "ID" {
result.id = Some(value.to_owned());
} else if key == "VERSION_ID" {
result.version_id = Some(value.to_owned());
}
}

line.clear();
}
}
}
}

result
}
}

fn parse_os_release_line(line: &str) -> Option<(&str, &str)> {
let line = line.trim();

let (key, value) = line.split_once('=')?;

// The value is essentially a shell string, so it can be quoted in single or
// double quotes, and can have escaped sequences using backslash.
// For simplicitly, just trim the quotes instead of implementing a full shell
// string grammar.
let value = if (value.starts_with('\'') && value.ends_with('\''))
|| (value.starts_with('"') && value.ends_with('"'))
{
&value[1..(value.len() - 1)]
} else {
value
};

Some((key, value))
}

#[derive(Clone, Debug, Default, Serialize)]
struct SystemInfo {
used_ram: String,
Expand Down
2 changes: 1 addition & 1 deletion http-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ hyper-openssl = { version = "0.9" }
hyper-proxy = { version = "0.9", features = ["openssl-tls"], default-features = false }
libc = "0.2"
log = "0.4"
nix = "0.18"
nix = "0.22"
openssl = { version = "0.10" }
openssl-sys = { version = "0.9" }
percent-encoding = "2"
Expand Down