cgroups_fs/lib.rs
1//! Cgroup-fs is a minimal wrapper around Linux Control Groups (cgroups) filesystem (usually
2//! mounted as `/sys/fs/cgroup`).
3//!
4//! # Examples
5//!
6//! ## Get memory usage from root cgroup
7//!
8//! ```
9//! let root_cgroup = cgroups_fs::CgroupName::new("");
10//! let root_memory_cgroup = cgroups_fs::Cgroup::new(&root_cgroup, "memory");
11//! println!(
12//! "Current memory usage is {} bytes",
13//! root_memory_cgroup.get_value::<u64>("memory.usage_in_bytes").unwrap()
14//! );
15//! ```
16//!
17//! ## Measure memory usage of a child process
18//!
19//! Read [the `CgroupsCommandExt` documentation].
20//!
21//! [the `CgroupsCommandExt` documentation]: trait.CgroupsCommandExt.html#impl-CgroupsCommandExt
22#![cfg(target_os = "linux")]
23#![deny(missing_docs)]
24
25use std::fs;
26use std::io;
27use std::os::unix::process::CommandExt;
28use std::path::{Path, PathBuf};
29
30use nix;
31
32/// A common structure holding a cgroups name (path).
33#[derive(Debug)]
34pub struct CgroupName {
35 mount_point: PathBuf,
36 name: PathBuf,
37}
38
39impl CgroupName {
40 /// Defines a new cgroups name.
41 ///
42 /// Notes:
43 /// * It does not create any cgroups. It is just an API abstraction layer. Learn more about
44 /// [`Cgroup::new`], [`Cgroup::create`], [`Cgroup::remove`], and [`AutomanagedCgroup::init`]
45 /// methods.
46 ///
47 /// [`Cgroup::new`]: struct.Cgroup.html#method.new
48 /// [`Cgroup::create`]: struct.Cgroup.html#method.create
49 /// [`Cgroup::remove`]: struct.Cgroup.html#method.remove
50 /// [`AutomanagedCgroup::init`]: struct.AutomanagedCgroup.html#method.init
51 pub fn new<P>(name: P) -> Self
52 where
53 P: AsRef<Path>,
54 {
55 Self {
56 // TODO: auto-discover the cgroups FS mount-point
57 mount_point: "/sys/fs/cgroup".into(),
58 name: name.as_ref().to_path_buf(),
59 }
60 }
61}
62
63/// A controller of a specific cgroups namespace.
64///
65/// This type supports a number of operations for manipulating with a cgroups namespace.
66#[derive(Debug)]
67pub struct Cgroup {
68 root: PathBuf,
69}
70
71impl Cgroup {
72 /// Defines a cgroup relation.
73 ///
74 /// Notes:
75 /// * It does not create any cgroups. It is just an API abstraction layer. Learn more about
76 /// [`Cgroup::create`], [`Cgroup::remove`], and [`AutomanagedCgroup::init`] methods.
77 ///
78 /// [`Cgroup::create`]: #method.create
79 /// [`Cgroup::remove`]: #method.remove
80 /// [`AutomanagedCgroup::init`]: struct.AutomanagedCgroup.html#method.init
81 pub fn new(cgroup_name: &CgroupName, subsystem: &str) -> Self {
82 Self {
83 root: cgroup_name
84 .mount_point
85 .join(subsystem)
86 .join(&cgroup_name.name),
87 }
88 }
89
90 /// Creates a cgroups namespace.
91 ///
92 /// Notes:
93 /// * Keep in mind the usual filesystem permissions (owner, group, and mode bits).
94 pub fn create(&self) -> io::Result<()> {
95 fs::create_dir(&self.root).map_err(|error| {
96 io::Error::new(
97 error.kind(),
98 format!(
99 "Cgroup cannot be created due to: {} (tried creating {:?} directory)",
100 error, self.root
101 ),
102 )
103 })
104 }
105
106 /// Removes a cgroups namespace.
107 ///
108 /// Notes:
109 /// * This method will fail if there are nested cgroups.
110 /// * Keep in mind the usual filesystem permissions (owner, group, and mode bits).
111 pub fn remove(&self) -> io::Result<()> {
112 fs::remove_dir(&self.root).map_err(|error| {
113 io::Error::new(
114 error.kind(),
115 format!(
116 "Cgroup cannot be removed due to: {} (tried removing {:?} directory)",
117 error, self.root
118 ),
119 )
120 })
121 }
122
123 /// Sets a binary or string value to the cgroup control file.
124 pub fn set_raw_value<V>(&self, key: &str, value: V) -> io::Result<()>
125 where
126 V: AsRef<[u8]>,
127 {
128 let key = self.root.join(key);
129 fs::write(&key, value).map_err(|error| {
130 io::Error::new(
131 error.kind(),
132 format!(
133 "Cgroup value under key {:?} cannot be set due to: {}",
134 key, error
135 ),
136 )
137 })
138 }
139
140 /// Sets a value to the cgroup control file.
141 pub fn set_value<V>(&self, key: &str, value: V) -> io::Result<()>
142 where
143 V: Copy + ToString,
144 {
145 self.set_raw_value(key, value.to_string())
146 }
147
148 /// Gets a string value from cgroup control file.
149 pub fn get_raw_value(&self, key: &str) -> io::Result<String> {
150 let key = self.root.join(key);
151 fs::read_to_string(&key).map_err(|error| {
152 io::Error::new(
153 error.kind(),
154 format!(
155 "Cgroup value under key {:?} cannot be read due to: {}",
156 key, error
157 ),
158 )
159 })
160 }
161
162 /// Gets a value from cgroup control file.
163 pub fn get_value<T>(&self, key: &str) -> io::Result<T>
164 where
165 T: std::str::FromStr,
166 {
167 self.get_raw_value(key)?
168 .trim_end()
169 .parse()
170 .map_err(|_| io::Error::new(io::ErrorKind::Other, "could not parse the value"))
171 }
172
173 fn tasks_absolute_path(&self) -> PathBuf {
174 self.root.join("tasks")
175 }
176
177 /// Attaches a task (thread) to the cgroup.
178 pub fn add_task(&self, pid: nix::unistd::Pid) -> io::Result<()> {
179 fs::write(self.tasks_absolute_path(), pid.to_string()).map_err(|error| {
180 io::Error::new(
181 error.kind(),
182 format!(
183 "A task cannot be added to cgroup {:?} due to: {}",
184 self.root, error
185 ),
186 )
187 })
188 }
189
190 /// Lists tasks (threads) attached to the cgroup.
191 pub fn get_tasks(&self) -> io::Result<Vec<nix::unistd::Pid>> {
192 Ok(fs::read_to_string(self.tasks_absolute_path())
193 .map_err(|error| {
194 io::Error::new(
195 error.kind(),
196 format!(
197 "Tasks cannot be read from cgroup {:?} due to: {}",
198 self.root, error
199 ),
200 )
201 })?
202 .split_whitespace()
203 .map(|pid| nix::unistd::Pid::from_raw(pid.parse().unwrap()))
204 .collect())
205 }
206
207 /// Sends a specified Unix Signal to all the tasks in the Cgroup.
208 pub fn send_signal_to_all_tasks(&self, signal: nix::sys::signal::Signal) -> io::Result<usize> {
209 let tasks = self.get_tasks()?;
210 let tasks_count = tasks.len();
211 for task in tasks {
212 nix::sys::signal::kill(task, signal).ok();
213 }
214 Ok(tasks_count)
215 }
216
217 /// Kills (SIGKILL) all the attached to the cgroup tasks.
218 ///
219 /// WARNING: The naive implementation turned out to be not reliable enough for the fork-bomb
220 /// use-case. To implement a reliable `kill_all` method, use `freezer` Cgroup. It is decided to
221 /// move such extensions into a separate crate (to be announced).
222 #[deprecated(
223 since = "1.0.1",
224 note = "please, use `freezer` cgroup to implement `kill_all_tasks` reliably (https://gitlab.com/dots.org.ua/ddots-runner/blob/d967ee3ba9de364dfb5a2e1a4f468586efb504f8/src/extensions/process.rs#L132-166)"
225 )]
226 pub fn kill_all_tasks(&self) -> io::Result<()> {
227 for _ in 0..100 {
228 if self.send_signal_to_all_tasks(nix::sys::signal::Signal::SIGKILL)? == 0 {
229 break;
230 }
231 std::thread::sleep(std::time::Duration::from_micros(1));
232 }
233 Err(io::Error::new(
234 io::ErrorKind::Other,
235 "child subprocess(es) survived SIGKILL",
236 ))
237 }
238}
239
240/// An automatically managed controller of a specific cgroups subsystem.
241///
242/// It is a wrapper around [`Cgroup`] type which automatically creates (on [`init`]) and removes
243/// (on [`drop`]) a cgroup in a given subsystem.
244///
245/// Since it is a wrapper, all the methods from [`Cgroup`] type are directly available for
246/// `AutomanagedCgroup` instances.
247///
248/// [`Cgroup`]: struct.Cgroup.html
249/// [`init`]: struct.AutomanagedCgroup.html#method.init
250/// [`drop`]: struct.AutomanagedCgroup.html#impl-Drop
251#[derive(Debug)]
252pub struct AutomanagedCgroup {
253 inner: Cgroup,
254}
255
256impl AutomanagedCgroup {
257 /// Inits a cgroup, which means that it creates a cgroup in a given subsystem.
258 ///
259 /// Notes:
260 /// * If there is an existing cgroup, it will be recreated from scratch. If that is not what
261 /// you what, consider using [`Cgroup`] type instead.
262 /// * The cgroup will be automatically removed once the `AutomanagedCgroup` instance is
263 /// dropped.
264 ///
265 /// [`Cgroup`]: struct.Cgroup.html
266 pub fn init(cgroup_name: &CgroupName, subsystem: &str) -> io::Result<Self> {
267 let inner = Cgroup::new(cgroup_name, subsystem);
268 if let Err(error) = inner.create() {
269 match inner.get_tasks() {
270 Err(_) => return Err(error),
271 Ok(tasks) => {
272 if !tasks.is_empty() {
273 return Err(error);
274 }
275 }
276 }
277 inner.remove().ok();
278 inner.create()?;
279 }
280 Ok(Self { inner })
281 }
282}
283
284impl std::ops::Deref for AutomanagedCgroup {
285 type Target = Cgroup;
286
287 fn deref(&self) -> &Self::Target {
288 &self.inner
289 }
290}
291
292impl AsRef<Cgroup> for AutomanagedCgroup {
293 fn as_ref(&self) -> &Cgroup {
294 &self
295 }
296}
297
298impl Drop for AutomanagedCgroup {
299 fn drop(&mut self) {
300 drop(self.inner.remove());
301 }
302}
303
304/// This trait is designed to extend `std::process::Command` type with helpers for Cgroups.
305pub trait CgroupsCommandExt {
306 /// Specifies the Cgroups the executed process will be put into on start.
307 fn cgroups(&mut self, cgroups: &[impl AsRef<Cgroup>]) -> &mut Self;
308}
309
310impl CgroupsCommandExt for std::process::Command {
311 /// Specifies the Cgroups the executed process will be put into on start.
312 ///
313 /// # Example
314 ///
315 /// ```no_run
316 /// let my_cgroup = cgroups_fs::CgroupName::new("my-cgroup");
317 /// let my_memory_cgroup = cgroups_fs::AutomanagedCgroup::init(&my_cgroup, "memory").unwrap();
318 ///
319 /// use cgroups_fs::CgroupsCommandExt;
320 /// let output = std::process::Command::new("echo")
321 /// .arg("Hello world")
322 /// .cgroups(&[&my_memory_cgroup])
323 /// .output()
324 /// .expect("Failed to execute command");
325 ///
326 /// println!(
327 /// "The echo process used {} bytes of RAM.",
328 /// my_memory_cgroup.get_value::<u64>("memory.max_usage_in_bytes").unwrap()
329 /// );
330 /// ```
331 fn cgroups(&mut self, cgroups: &[impl AsRef<Cgroup>]) -> &mut Self {
332 let tasks_paths = cgroups
333 .iter()
334 .map(|cgroup| cgroup.as_ref().tasks_absolute_path())
335 .collect::<Vec<PathBuf>>();
336 unsafe {
337 self.pre_exec(move || {
338 let pid = std::process::id().to_string();
339 for tasks_path in &tasks_paths {
340 fs::write(tasks_path, &pid)?;
341 }
342 Ok(())
343 })
344 }
345 }
346}