Skip to content

Commit

Permalink
[release/1.2] Move system information gathering to common crate (#353)
Browse files Browse the repository at this point in the history
*Cf.* #342.
  • Loading branch information
onalante-msft authored Jan 20, 2022
1 parent fb2770e commit 8acaa7a
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 108 deletions.
33 changes: 17 additions & 16 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions aziotctl/aziotctl-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ log = "0.4"
nix = "0.23"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.59"
serde_with = "1.11"
url = { version = "2", features = ["serde"] }

aziot-certd-config = { path = "../../cert/aziot-certd-config" }
Expand Down
139 changes: 139 additions & 0 deletions aziotctl/aziotctl-common/src/host_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// 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 | vendor
/// ---------+-----------------+-----------------------
/// Hyper-V | Virtual Machine | Microsoft Corporation
/// ```
///
/// Ref: <https://www.kernel.org/doc/html/latest/filesystems/sysfs.html>
#[derive(Clone, Debug, Serialize)]
pub struct DmiInfo {
pub product: Option<String>,
pub vendor: Option<String>,
}

impl Default for DmiInfo {
fn default() -> Self {
Self {
product: try_read_dmi("product_name"),
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 variant_id: Option<String>,
pub build_id: 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,
variant_id: None,
build_id: 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(("VARIANT_ID", value)) => {
result.variant_id = Some(value.to_owned());
}
Some(("BUILD_ID", value)) => {
result.build_id = Some(value.to_owned());
}
_ => (),
};
} else {
break;
}
}
}

result
}
}

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

let pos = line.find('=')?;
let (key, value) = (&line[..pos], &line[pos + 1..]);

// 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))
}

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(std::str::from_utf8(&bytes).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
97 changes: 5 additions & 92 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,94 +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 mut parts = line.split('=');

let key = parts
.next()
.expect("split line will have at least one part");

let value = parts.next()?;

// 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

0 comments on commit 8acaa7a

Please sign in to comment.