blob: 50ca3e8bfe8db0bf32a722540a31504cc3f8ac5f [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This file contains a very basic implementation of control groups.
//!
//! There is no support for actual resource constraints, or any operations outside of adding tasks
//! to a control group (for the duration of their lifetime).
use crate::{
task::{CurrentTask, Task},
vfs::{
buffers::InputBuffer, fileops_impl_delegate_read_and_seek, fs_node_impl_not_dir,
DynamicFile, DynamicFileBuf, DynamicFileSource, FileObject, FileOps, FsNode, FsNodeHandle,
FsNodeInfo, FsNodeOps, FsStr, MemoryDirectoryFile,
},
};
use starnix_sync::{FileOpsCore, Locked, Mutex, WriteOps};
use starnix_uapi::{
auth::FsCred, device_type::DeviceType, errno, error, errors::Errno, file_mode::FileMode,
open_flags::OpenFlags, ownership::WeakRef, pid_t,
};
use std::sync::Arc;
type ControlGroupHandle = Arc<Mutex<ControlGroup>>;
struct ControlGroup {
/// The tasks that are part of this control group.
tasks: Vec<WeakRef<Task>>,
}
impl ControlGroup {
fn new() -> ControlGroupHandle {
Arc::new(Mutex::new(Self { tasks: vec![] }))
}
}
/// A `CgroupDirectoryNode` represents the node associated with a particular control group. A
/// control group node may have other control groups as children, each of which will also be
/// represented as a `CgroupDirectoryNode`.
pub struct CgroupDirectoryNode {
/// The control group associated with this directory node.
control_group: ControlGroupHandle,
}
impl CgroupDirectoryNode {
pub fn new() -> Self {
Self { control_group: ControlGroup::new() }
}
}
impl FsNodeOps for CgroupDirectoryNode {
fn create_file_ops(
&self,
_locked: &mut Locked<'_, FileOpsCore>,
_node: &FsNode,
_current_task: &CurrentTask,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
Ok(Box::new(MemoryDirectoryFile::new()))
}
fn mkdir(
&self,
node: &FsNode,
current_task: &CurrentTask,
_name: &FsStr,
mode: FileMode,
owner: FsCred,
) -> Result<FsNodeHandle, Errno> {
node.update_info(|info| {
info.link_count += 1;
});
Ok(node.fs().create_node(
current_task,
CgroupDirectoryNode::new(),
FsNodeInfo::new_factory(mode, owner),
))
}
fn mknod(
&self,
_locked: &mut Locked<'_, FileOpsCore>,
node: &FsNode,
current_task: &CurrentTask,
name: &FsStr,
mode: FileMode,
dev: DeviceType,
owner: FsCred,
) -> Result<FsNodeHandle, Errno> {
// TODO(lindkvist): Handle files that are not `cgroup.procs`.
if name != FsStr::new("cgroup.procs") {
return error!(EACCES);
}
let ops: Box<dyn FsNodeOps> = match mode.fmt() {
FileMode::IFREG => Box::new(ControlGroupNode::new(self.control_group.clone())),
_ => return error!(EACCES),
};
let node = node.fs().create_node(current_task, ops, |id| {
let mut info = FsNodeInfo::new(id, mode, owner);
info.rdev = dev;
info
});
Ok(node)
}
fn unlink(
&self,
_locked: &mut Locked<'_, FileOpsCore>,
_node: &FsNode,
_current_task: &CurrentTask,
_name: &FsStr,
_child: &FsNodeHandle,
) -> Result<(), Errno> {
error!(EPERM)
}
fn create_symlink(
&self,
_node: &FsNode,
_current_task: &CurrentTask,
_name: &FsStr,
_target: &FsStr,
_owner: FsCred,
) -> Result<FsNodeHandle, Errno> {
error!(EPERM)
}
}
/// A `ControlGroupNode` backs the `cgroup.procs` file.
///
/// Opening and writing to this node will add tasks to the control group.
struct ControlGroupNode {
control_group: ControlGroupHandle,
}
impl ControlGroupNode {
fn new(control_group: ControlGroupHandle) -> Self {
ControlGroupNode { control_group }
}
}
impl FsNodeOps for ControlGroupNode {
fs_node_impl_not_dir!();
fn create_file_ops(
&self,
_locked: &mut Locked<'_, FileOpsCore>,
_node: &FsNode,
_current_task: &CurrentTask,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
Ok(Box::new(ControlGroupFile::new(self.control_group.clone())))
}
fn truncate(
&self,
_locked: &mut Locked<'_, FileOpsCore>,
_node: &FsNode,
_current_task: &CurrentTask,
_length: u64,
) -> Result<(), Errno> {
Ok(())
}
}
/// A `ControlGroupFile` currently represents the `cgroup.procs` file for the control group. Writing
/// to this file will add tasks to the control group.
struct ControlGroupFileSource {
control_group: ControlGroupHandle,
}
impl DynamicFileSource for ControlGroupFileSource {
fn generate(&self, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
let mut pids: Vec<pid_t> = vec![];
self.control_group.lock().tasks.retain(|t| {
if let Some(t) = t.upgrade() {
pids.push(t.get_pid());
true
} else {
// Filter out the tasks that have been dropped.
false
}
});
for pid in pids {
write!(sink, "{pid}")?;
}
Ok(())
}
}
pub struct ControlGroupFile {
control_group: ControlGroupHandle,
dynamic_file: DynamicFile<ControlGroupFileSource>,
}
impl ControlGroupFile {
fn new(control_group: ControlGroupHandle) -> Self {
Self {
control_group: control_group.clone(),
dynamic_file: DynamicFile::new(ControlGroupFileSource { control_group }),
}
}
}
impl FileOps for ControlGroupFile {
fileops_impl_delegate_read_and_seek!(self, self.dynamic_file);
fn write(
&self,
_locked: &mut Locked<'_, WriteOps>,
_file: &FileObject,
current_task: &CurrentTask,
_offset: usize,
data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
let bytes = data.read_all()?;
let pid_string = std::str::from_utf8(&bytes).map_err(|_| errno!(EINVAL))?;
let pid = pid_string.parse::<pid_t>().map_err(|_| errno!(ENOENT))?;
let weak_task = current_task.get_task(pid);
let task = weak_task.upgrade().ok_or_else(|| errno!(EINVAL))?;
// TODO(lindkvist): The task needs to be removed form any existing control group before
// being added to a new one.
self.control_group.lock().tasks.push(WeakRef::from(&task));
Ok(bytes.len())
}
}