Skip to content

Commit f803d76

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 f803d76

File tree

8 files changed

+322
-0
lines changed

8 files changed

+322
-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: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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::{set_exit_code, 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::UResult;
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) -> UResult<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) -> UResult<()> {
60+
std::fs::write(variable_path(var), value)?;
61+
Ok(())
62+
}
63+
}
64+
#[cfg(target_os = "linux")]
65+
use linux::*;
66+
67+
#[cfg(target_os = "linux")]
68+
#[uucore::main]
69+
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
70+
let matches = uu_app().try_get_matches_from(args)?;
71+
72+
let vars = if matches.get_flag("all") {
73+
get_all_sysctl_variables()
74+
} else if let Some(vars) = matches.get_many::<String>("variables") {
75+
vars.cloned().collect()
76+
} else {
77+
uu_app().print_help()?;
78+
return Ok(());
79+
};
80+
81+
for var_or_assignment in vars {
82+
let mut splitted = var_or_assignment.splitn(2, '=');
83+
let var = normalize_var(splitted.next().unwrap());
84+
85+
let value_to_print = if let Some(value_to_set) = splitted.next() {
86+
if let Err(e) = set_sysctl(&var, value_to_set) {
87+
if !matches.get_flag("ignore") {
88+
uucore::show_error!("error writing key '{}': {}", var, e);
89+
set_exit_code(1);
90+
}
91+
continue;
92+
}
93+
if matches.get_flag("quiet") {
94+
continue;
95+
}
96+
value_to_set.to_string()
97+
} else {
98+
match get_sysctl(&var) {
99+
Ok(v) => v,
100+
Err(e) => {
101+
if !matches.get_flag("ignore") {
102+
uucore::show_error!("error reading key '{}': {}", var, e);
103+
set_exit_code(1);
104+
}
105+
continue;
106+
}
107+
}
108+
};
109+
for line in value_to_print.split('\n') {
110+
if matches.get_flag("names") {
111+
println!("{}", var);
112+
} else if matches.get_flag("values") {
113+
println!("{}", line);
114+
} else {
115+
println!("{} = {}", var, line);
116+
}
117+
}
118+
}
119+
120+
Ok(())
121+
}
122+
123+
#[cfg(not(target_os = "linux"))]
124+
#[uucore::main]
125+
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
126+
let _matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;
127+
128+
Err(uucore::error::USimpleError::new(
129+
1,
130+
"`sysctl` currently only supports Linux.",
131+
))
132+
}
133+
134+
pub fn uu_app() -> Command {
135+
Command::new(uucore::util_name())
136+
.version(crate_version!())
137+
.about(ABOUT)
138+
.override_usage(format_usage(USAGE))
139+
.infer_long_args(true)
140+
.arg(
141+
Arg::new("variables")
142+
.value_name("VARIABLE[=VALUE]")
143+
.action(ArgAction::Append),
144+
)
145+
.arg(
146+
Arg::new("all")
147+
.short('a')
148+
.visible_short_aliases(['A', 'X'])
149+
.long("all")
150+
.action(ArgAction::SetTrue)
151+
.help("Display all variables"),
152+
)
153+
.arg(
154+
Arg::new("names")
155+
.short('N')
156+
.long("names")
157+
.action(ArgAction::SetTrue)
158+
.help("Only print names"),
159+
)
160+
.arg(
161+
Arg::new("values")
162+
.short('n')
163+
.long("values")
164+
.action(ArgAction::SetTrue)
165+
.help("Only print values"),
166+
)
167+
.arg(
168+
Arg::new("ignore")
169+
.short('e')
170+
.long("ignore")
171+
.action(ArgAction::SetTrue)
172+
.help("Ignore errors"),
173+
)
174+
.arg(
175+
Arg::new("quiet")
176+
.short('q')
177+
.long("quiet")
178+
.action(ArgAction::SetTrue)
179+
.help("Do not print when setting variables"),
180+
)
181+
.arg(
182+
Arg::new("noop_o")
183+
.short('o')
184+
.help("Does nothing, for BSD compatibility"),
185+
)
186+
.arg(
187+
Arg::new("noop_x")
188+
.short('x')
189+
.help("Does nothing, for BSD compatibility"),
190+
)
191+
}

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)