Skip to content

Commit 5dfcdb0

Browse files
committed
sysctl: Add tool
This supports basic get and set of sysctls on Linux. Main missing thing is reading /etc/sysctl.d files.
1 parent ab6e081 commit 5dfcdb0

File tree

8 files changed

+330
-0
lines changed

8 files changed

+330
-0
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ feat_common_core = [
3636
"slabtop",
3737
"snice",
3838
"pkill",
39+
"sysctl",
3940
"top",
4041
"w",
4142
"watch",
@@ -84,6 +85,7 @@ pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu
8485
slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" }
8586
snice = { optional = true, version = "0.0.1", package = "uu_snice", path = "src/uu/snice" }
8687
pkill = { optional = true, version = "0.0.1", package = "uu_pkill", path = "src/uu/pkill" }
88+
sysctl = { optional = true, version = "0.0.1", package = "uu_sysctl", path = "src/uu/sysctl" }
8789
top = { optional = true, version = "0.0.1", package = "uu_top", path = "src/uu/top" }
8890
w = { optional = true, version = "0.0.1", package = "uu_w", path = "src/uu/w" }
8991
watch = { optional = true, version = "0.0.1", package = "uu_watch", path = "src/uu/watch" }

src/uu/sysctl/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "uu_sysctl"
3+
version = "0.0.1"
4+
edition = "2021"
5+
authors = ["uutils developers"]
6+
license = "MIT"
7+
description = "sysctl ~ (uutils) Show or modify kernel parameters at runtime"
8+
9+
homepage = "https://github.com/uutils/procps"
10+
repository = "https://github.com/uutils/procps/tree/main/src/uu/sysctl"
11+
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
12+
categories = ["command-line-utilities"]
13+
14+
[dependencies]
15+
uucore = { workspace = true }
16+
clap = { workspace = true }
17+
sysinfo = { workspace = true }
18+
walkdir = { workspace = true }
19+
20+
[lib]
21+
path = "src/sysctl.rs"
22+
23+
[[bin]]
24+
name = "sysctl"
25+
path = "src/main.rs"

src/uu/sysctl/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uucore::bin!(uu_sysctl);

src/uu/sysctl/src/sysctl.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use clap::{crate_version, Arg, ArgAction, Command};
7+
use std::env;
8+
use uucore::error::UResult;
9+
use uucore::{format_usage, help_about, help_usage};
10+
11+
const ABOUT: &str = help_about!("sysctl.md");
12+
const USAGE: &str = help_usage!("sysctl.md");
13+
14+
#[cfg(target_os = "linux")]
15+
mod linux {
16+
use std::path::{Path, PathBuf};
17+
use uucore::error::{FromIo, UIoError};
18+
use walkdir::WalkDir;
19+
20+
const PROC_SYS_ROOT: &str = "/proc/sys";
21+
22+
pub fn get_all_sysctl_variables() -> Vec<String> {
23+
let mut ret = vec![];
24+
for entry in WalkDir::new(PROC_SYS_ROOT) {
25+
match entry {
26+
Ok(e) => {
27+
if e.file_type().is_file() {
28+
let var = e
29+
.path()
30+
.strip_prefix(PROC_SYS_ROOT)
31+
.expect("Always should be ancestor of of sysctl root");
32+
if let Some(s) = var.as_os_str().to_str() {
33+
ret.push(s.to_owned());
34+
}
35+
}
36+
}
37+
Err(e) => {
38+
uucore::show_error!("{}", e);
39+
}
40+
}
41+
}
42+
ret
43+
}
44+
45+
pub fn normalize_var(var: &str) -> String {
46+
var.replace('/', ".")
47+
}
48+
49+
pub fn variable_path(var: &str) -> PathBuf {
50+
Path::new(PROC_SYS_ROOT).join(var.replace('.', "/"))
51+
}
52+
53+
pub fn get_sysctl(var: &str) -> std::io::Result<String> {
54+
Ok(std::fs::read_to_string(variable_path(var))?
55+
.trim_end()
56+
.to_string())
57+
}
58+
59+
pub fn set_sysctl(var: &str, value: &str) -> std::io::Result<()> {
60+
std::fs::write(variable_path(var), value)
61+
}
62+
63+
pub fn handle_one_arg(
64+
var_or_assignment: &str,
65+
quiet: bool,
66+
) -> Result<Option<(String, String)>, Box<UIoError>> {
67+
let mut splitted = var_or_assignment.splitn(2, '=');
68+
let var = normalize_var(
69+
splitted
70+
.next()
71+
.expect("Split always returns at least 1 value"),
72+
);
73+
74+
if let Some(value_to_set) = splitted.next() {
75+
set_sysctl(&var, value_to_set)
76+
.map_err(|e| e.map_err_context(|| format!("error writing key '{}'", var)))?;
77+
if quiet {
78+
Ok(None)
79+
} else {
80+
Ok(Some((var, value_to_set.to_string())))
81+
}
82+
} else {
83+
let value = get_sysctl(&var)
84+
.map_err(|e| e.map_err_context(|| format!("error reading key '{}'", var)))?;
85+
Ok(Some((var, value)))
86+
}
87+
}
88+
}
89+
#[cfg(target_os = "linux")]
90+
use linux::*;
91+
92+
#[cfg(target_os = "linux")]
93+
#[uucore::main]
94+
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
95+
let matches = uu_app().try_get_matches_from(args)?;
96+
97+
let vars = if matches.get_flag("all") {
98+
get_all_sysctl_variables()
99+
} else if let Some(vars) = matches.get_many::<String>("variables") {
100+
vars.cloned().collect()
101+
} else {
102+
uu_app().print_help()?;
103+
return Ok(());
104+
};
105+
106+
for var_or_assignment in vars {
107+
match handle_one_arg(&var_or_assignment, matches.get_flag("quiet")) {
108+
Ok(None) => (),
109+
Ok(Some((var, value_to_print))) => {
110+
for line in value_to_print.split('\n') {
111+
if matches.get_flag("names") {
112+
println!("{}", var);
113+
} else if matches.get_flag("values") {
114+
println!("{}", line);
115+
} else {
116+
println!("{} = {}", var, line);
117+
}
118+
}
119+
}
120+
Err(e) => {
121+
if !matches.get_flag("ignore") {
122+
uucore::show!(e);
123+
}
124+
}
125+
}
126+
}
127+
128+
Ok(())
129+
}
130+
131+
#[cfg(not(target_os = "linux"))]
132+
#[uucore::main]
133+
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
134+
let _matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;
135+
136+
Err(uucore::error::USimpleError::new(
137+
1,
138+
"`sysctl` currently only supports Linux.",
139+
))
140+
}
141+
142+
pub fn uu_app() -> Command {
143+
Command::new(uucore::util_name())
144+
.version(crate_version!())
145+
.about(ABOUT)
146+
.override_usage(format_usage(USAGE))
147+
.infer_long_args(true)
148+
.arg(
149+
Arg::new("variables")
150+
.value_name("VARIABLE[=VALUE]")
151+
.action(ArgAction::Append),
152+
)
153+
.arg(
154+
Arg::new("all")
155+
.short('a')
156+
.visible_short_aliases(['A', 'X'])
157+
.long("all")
158+
.action(ArgAction::SetTrue)
159+
.help("Display all variables"),
160+
)
161+
.arg(
162+
Arg::new("names")
163+
.short('N')
164+
.long("names")
165+
.action(ArgAction::SetTrue)
166+
.help("Only print names"),
167+
)
168+
.arg(
169+
Arg::new("values")
170+
.short('n')
171+
.long("values")
172+
.action(ArgAction::SetTrue)
173+
.help("Only print values"),
174+
)
175+
.arg(
176+
Arg::new("ignore")
177+
.short('e')
178+
.long("ignore")
179+
.action(ArgAction::SetTrue)
180+
.help("Ignore errors"),
181+
)
182+
.arg(
183+
Arg::new("quiet")
184+
.short('q')
185+
.long("quiet")
186+
.action(ArgAction::SetTrue)
187+
.help("Do not print when setting variables"),
188+
)
189+
.arg(
190+
Arg::new("noop_o")
191+
.short('o')
192+
.help("Does nothing, for BSD compatibility"),
193+
)
194+
.arg(
195+
Arg::new("noop_x")
196+
.short('x')
197+
.help("Does nothing, for BSD compatibility"),
198+
)
199+
}

src/uu/sysctl/sysctl.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# sysctl
2+
3+
```
4+
sysctl [options] [variable[=value]]...
5+
```
6+
7+
Show or modify kernel parameters at runtime.

tests/by-util/test_sysctl.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use crate::common::util::TestScenario;
7+
8+
#[test]
9+
fn test_invalid_arg() {
10+
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
11+
}
12+
13+
#[cfg(target_os = "linux")]
14+
mod linux {
15+
use crate::common::util::TestScenario;
16+
17+
#[test]
18+
fn test_get_simple() {
19+
new_ucmd!()
20+
.arg("kernel.ostype")
21+
.arg("fs.overflowuid")
22+
.succeeds()
23+
.stdout_is("kernel.ostype = Linux\nfs.overflowuid = 65534\n");
24+
}
25+
26+
#[test]
27+
fn test_get_value_only() {
28+
new_ucmd!()
29+
.arg("-n")
30+
.arg("kernel.ostype")
31+
.arg("fs.overflowuid")
32+
.succeeds()
33+
.stdout_is("Linux\n65534\n");
34+
}
35+
36+
#[test]
37+
fn test_get_key_only() {
38+
new_ucmd!()
39+
.arg("-N")
40+
.arg("kernel.ostype")
41+
.arg("fs.overflowuid")
42+
.succeeds()
43+
.stdout_is("kernel.ostype\nfs.overflowuid\n");
44+
}
45+
46+
#[test]
47+
fn test_continues_on_error() {
48+
new_ucmd!()
49+
.arg("nonexisting")
50+
.arg("kernel.ostype")
51+
.fails()
52+
.stdout_is("kernel.ostype = Linux\n")
53+
.stderr_is("sysctl: error reading key 'nonexisting': No such file or directory\n");
54+
}
55+
56+
#[test]
57+
fn test_ignoring_errors() {
58+
new_ucmd!()
59+
.arg("-e")
60+
.arg("nonexisting")
61+
.arg("nonexisting2=foo")
62+
.arg("kernel.ostype")
63+
.succeeds()
64+
.stdout_is("kernel.ostype = Linux\n")
65+
.stderr_is("");
66+
}
67+
}
68+
69+
#[cfg(not(target_os = "linux"))]
70+
mod non_linux {
71+
use crate::common::util::TestScenario;
72+
73+
#[test]
74+
fn test_fails_on_unsupported_platforms() {
75+
new_ucmd!()
76+
.arg("-a")
77+
.fails()
78+
.code_is(1)
79+
.stderr_is("sysctl: `sysctl` currently only supports Linux.\n");
80+
}
81+
}

tests/tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ mod test_snice;
5656
#[cfg(feature = "pkill")]
5757
#[path = "by-util/test_pkill.rs"]
5858
mod test_pkill;
59+
60+
#[cfg(feature = "sysctl")]
61+
#[path = "by-util/test_sysctl.rs"]
62+
mod test_sysctl;

0 commit comments

Comments
 (0)