Skip to content

Commit

Permalink
sysctl: Add tool
Browse files Browse the repository at this point in the history
This supports basic get and set of sysctls on Linux. Main missing thing
is reading /etc/sysctl.d files.
  • Loading branch information
dezgeg committed Jan 18, 2025
1 parent ab6e081 commit 5dfcdb0
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ feat_common_core = [
"slabtop",
"snice",
"pkill",
"sysctl",
"top",
"w",
"watch",
Expand Down Expand Up @@ -84,6 +85,7 @@ pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu
slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" }
snice = { optional = true, version = "0.0.1", package = "uu_snice", path = "src/uu/snice" }
pkill = { optional = true, version = "0.0.1", package = "uu_pkill", path = "src/uu/pkill" }
sysctl = { optional = true, version = "0.0.1", package = "uu_sysctl", path = "src/uu/sysctl" }
top = { optional = true, version = "0.0.1", package = "uu_top", path = "src/uu/top" }
w = { optional = true, version = "0.0.1", package = "uu_w", path = "src/uu/w" }
watch = { optional = true, version = "0.0.1", package = "uu_watch", path = "src/uu/watch" }
Expand Down
25 changes: 25 additions & 0 deletions src/uu/sysctl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "uu_sysctl"
version = "0.0.1"
edition = "2021"
authors = ["uutils developers"]
license = "MIT"
description = "sysctl ~ (uutils) Show or modify kernel parameters at runtime"

homepage = "https://github.com/uutils/procps"
repository = "https://github.com/uutils/procps/tree/main/src/uu/sysctl"
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]

[dependencies]
uucore = { workspace = true }
clap = { workspace = true }
sysinfo = { workspace = true }
walkdir = { workspace = true }

[lib]
path = "src/sysctl.rs"

[[bin]]
name = "sysctl"
path = "src/main.rs"
1 change: 1 addition & 0 deletions src/uu/sysctl/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uucore::bin!(uu_sysctl);
199 changes: 199 additions & 0 deletions src/uu/sysctl/src/sysctl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// This file is part of the uutils procps package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use clap::{crate_version, Arg, ArgAction, Command};
use std::env;
use uucore::error::UResult;
use uucore::{format_usage, help_about, help_usage};

const ABOUT: &str = help_about!("sysctl.md");
const USAGE: &str = help_usage!("sysctl.md");

#[cfg(target_os = "linux")]
mod linux {
use std::path::{Path, PathBuf};
use uucore::error::{FromIo, UIoError};
use walkdir::WalkDir;

const PROC_SYS_ROOT: &str = "/proc/sys";

pub fn get_all_sysctl_variables() -> Vec<String> {
let mut ret = vec![];
for entry in WalkDir::new(PROC_SYS_ROOT) {
match entry {
Ok(e) => {
if e.file_type().is_file() {
let var = e
.path()
.strip_prefix(PROC_SYS_ROOT)
.expect("Always should be ancestor of of sysctl root");
if let Some(s) = var.as_os_str().to_str() {
ret.push(s.to_owned());
}
}
}
Err(e) => {
uucore::show_error!("{}", e);
}
}
}
ret
}

pub fn normalize_var(var: &str) -> String {
var.replace('/', ".")
}

pub fn variable_path(var: &str) -> PathBuf {
Path::new(PROC_SYS_ROOT).join(var.replace('.', "/"))
}

pub fn get_sysctl(var: &str) -> std::io::Result<String> {
Ok(std::fs::read_to_string(variable_path(var))?
.trim_end()
.to_string())
}

pub fn set_sysctl(var: &str, value: &str) -> std::io::Result<()> {
std::fs::write(variable_path(var), value)
}

pub fn handle_one_arg(
var_or_assignment: &str,
quiet: bool,
) -> Result<Option<(String, String)>, Box<UIoError>> {
let mut splitted = var_or_assignment.splitn(2, '=');
let var = normalize_var(
splitted
.next()
.expect("Split always returns at least 1 value"),
);

if let Some(value_to_set) = splitted.next() {
set_sysctl(&var, value_to_set)
.map_err(|e| e.map_err_context(|| format!("error writing key '{}'", var)))?;
if quiet {
Ok(None)
} else {
Ok(Some((var, value_to_set.to_string())))
}
} else {
let value = get_sysctl(&var)
.map_err(|e| e.map_err_context(|| format!("error reading key '{}'", var)))?;
Ok(Some((var, value)))
}
}
}
#[cfg(target_os = "linux")]
use linux::*;

#[cfg(target_os = "linux")]
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;

let vars = if matches.get_flag("all") {
get_all_sysctl_variables()
} else if let Some(vars) = matches.get_many::<String>("variables") {
vars.cloned().collect()
} else {
uu_app().print_help()?;
return Ok(());
};

for var_or_assignment in vars {
match handle_one_arg(&var_or_assignment, matches.get_flag("quiet")) {
Ok(None) => (),
Ok(Some((var, value_to_print))) => {
for line in value_to_print.split('\n') {
if matches.get_flag("names") {
println!("{}", var);
} else if matches.get_flag("values") {
println!("{}", line);
} else {
println!("{} = {}", var, line);
}
}
}
Err(e) => {
if !matches.get_flag("ignore") {
uucore::show!(e);
}
}
}
}

Ok(())
}

#[cfg(not(target_os = "linux"))]
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let _matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;

Err(uucore::error::USimpleError::new(
1,
"`sysctl` currently only supports Linux.",
))
}

pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(
Arg::new("variables")
.value_name("VARIABLE[=VALUE]")
.action(ArgAction::Append),
)
.arg(
Arg::new("all")
.short('a')
.visible_short_aliases(['A', 'X'])
.long("all")
.action(ArgAction::SetTrue)
.help("Display all variables"),
)
.arg(
Arg::new("names")
.short('N')
.long("names")
.action(ArgAction::SetTrue)
.help("Only print names"),
)
.arg(
Arg::new("values")
.short('n')
.long("values")
.action(ArgAction::SetTrue)
.help("Only print values"),
)
.arg(
Arg::new("ignore")
.short('e')
.long("ignore")
.action(ArgAction::SetTrue)
.help("Ignore errors"),
)
.arg(
Arg::new("quiet")
.short('q')
.long("quiet")
.action(ArgAction::SetTrue)
.help("Do not print when setting variables"),
)
.arg(
Arg::new("noop_o")
.short('o')
.help("Does nothing, for BSD compatibility"),
)
.arg(
Arg::new("noop_x")
.short('x')
.help("Does nothing, for BSD compatibility"),
)
}
7 changes: 7 additions & 0 deletions src/uu/sysctl/sysctl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# sysctl

```
sysctl [options] [variable[=value]]...
```

Show or modify kernel parameters at runtime.
81 changes: 81 additions & 0 deletions tests/by-util/test_sysctl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// This file is part of the uutils procps package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use crate::common::util::TestScenario;

#[test]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
}

#[cfg(target_os = "linux")]
mod linux {
use crate::common::util::TestScenario;

#[test]
fn test_get_simple() {
new_ucmd!()
.arg("kernel.ostype")
.arg("fs.overflowuid")
.succeeds()
.stdout_is("kernel.ostype = Linux\nfs.overflowuid = 65534\n");
}

#[test]
fn test_get_value_only() {
new_ucmd!()
.arg("-n")
.arg("kernel.ostype")
.arg("fs.overflowuid")
.succeeds()
.stdout_is("Linux\n65534\n");
}

#[test]
fn test_get_key_only() {
new_ucmd!()
.arg("-N")
.arg("kernel.ostype")
.arg("fs.overflowuid")
.succeeds()
.stdout_is("kernel.ostype\nfs.overflowuid\n");
}

#[test]
fn test_continues_on_error() {
new_ucmd!()
.arg("nonexisting")
.arg("kernel.ostype")
.fails()
.stdout_is("kernel.ostype = Linux\n")
.stderr_is("sysctl: error reading key 'nonexisting': No such file or directory\n");
}

#[test]
fn test_ignoring_errors() {
new_ucmd!()
.arg("-e")
.arg("nonexisting")
.arg("nonexisting2=foo")
.arg("kernel.ostype")
.succeeds()
.stdout_is("kernel.ostype = Linux\n")
.stderr_is("");
}
}

#[cfg(not(target_os = "linux"))]
mod non_linux {
use crate::common::util::TestScenario;

#[test]
fn test_fails_on_unsupported_platforms() {
new_ucmd!()
.arg("-a")
.fails()
.code_is(1)
.stderr_is("sysctl: `sysctl` currently only supports Linux.\n");
}
}
4 changes: 4 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ mod test_snice;
#[cfg(feature = "pkill")]
#[path = "by-util/test_pkill.rs"]
mod test_pkill;

#[cfg(feature = "sysctl")]
#[path = "by-util/test_sysctl.rs"]
mod test_sysctl;

0 comments on commit 5dfcdb0

Please sign in to comment.