Support for querying what realm a process belongs to
This commit is contained in:
parent
94a0fd210c
commit
1136cee2f7
@ -34,6 +34,7 @@ pub use crate::exec::{Exec,FileRange};
|
||||
pub use crate::realmfs::resizer::ResizeSize;
|
||||
pub use crate::realm::overlay::RealmOverlay;
|
||||
pub use crate::realm::realm::Realm;
|
||||
pub use crate::realm::pidmapper::PidLookupResult;
|
||||
pub use crate::realm::config::{RealmConfig,OverlayType,GLOBAL_CONFIG};
|
||||
pub use crate::realm::events::RealmEvent;
|
||||
pub use crate::realm::realms::Realms;
|
||||
|
@ -1,9 +1,10 @@
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use posix_acl::{ACL_EXECUTE, ACL_READ, PosixACL, Qualifier};
|
||||
|
||||
use crate::{Mountpoint, Result, Realms, RealmFS, Realm, util};
|
||||
use crate::realm::pidmapper::{PidLookupResult, PidMapper};
|
||||
use crate::realmfs::realmfs_set::RealmFSSet;
|
||||
|
||||
use super::systemd::Systemd;
|
||||
@ -333,27 +334,9 @@ impl RealmManager {
|
||||
self.inner().realms.by_name(name)
|
||||
}
|
||||
|
||||
pub fn realm_by_pid(&self, pid: u32) -> Option<Realm> {
|
||||
match Self::read_realm_name_by_pid(pid) {
|
||||
Ok(name) => self.realm_by_name(name.as_str()),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_realm_name_by_pid(pid: u32) -> Result<String> {
|
||||
let run = PathBuf::from(format!("/proc/{}/root/run", pid));
|
||||
let realm_name = run.join("realm-name");
|
||||
|
||||
// ensure that /proc/pid/root/run and /proc/pid/root/run/realm-name
|
||||
// are not symlinks
|
||||
let run_meta = run.symlink_metadata()
|
||||
.map_err(context!("failed reading symlink metadata {:?}", run))?;
|
||||
let name_meta = realm_name.symlink_metadata()
|
||||
.map_err(context!("failed reading symlink metadata {:?}", realm_name))?;
|
||||
if !run_meta.file_type().is_dir() || !name_meta.file_type().is_file() {
|
||||
bail!("invalid path");
|
||||
}
|
||||
util::read_to_string(&realm_name)
|
||||
pub fn realm_by_pid(&self, pid: u32) -> PidLookupResult {
|
||||
let mapper = PidMapper::new(self.active_realms(false));
|
||||
mapper.lookup_pid(pid as libc::pid_t)
|
||||
}
|
||||
|
||||
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
|
||||
|
@ -8,6 +8,7 @@ pub(crate) mod realm;
|
||||
pub (crate) mod network;
|
||||
pub(crate) mod create;
|
||||
pub(crate) mod events;
|
||||
pub(crate) mod pidmapper;
|
||||
mod systemd;
|
||||
mod launcher;
|
||||
|
||||
|
112
libcitadel/src/realm/pidmapper.rs
Normal file
112
libcitadel/src/realm/pidmapper.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use std::ffi::OsStr;
|
||||
use procfs::process::Process;
|
||||
use crate::Realm;
|
||||
|
||||
pub enum PidLookupResult {
|
||||
Unknown,
|
||||
Realm(Realm),
|
||||
Citadel,
|
||||
}
|
||||
|
||||
pub struct PidMapper {
|
||||
active_realms: Vec<Realm>,
|
||||
my_pid_ns_id: Option<u64>,
|
||||
}
|
||||
|
||||
impl PidMapper {
|
||||
pub fn new(active_realms: Vec<Realm>) -> Self {
|
||||
let my_pid_ns_id = Self::self_pid_namespace_id();
|
||||
PidMapper { active_realms, my_pid_ns_id }
|
||||
}
|
||||
|
||||
fn read_process(pid: libc::pid_t) -> Option<Process> {
|
||||
match Process::new(pid) {
|
||||
Ok(proc) => Some(proc),
|
||||
Err(err) => {
|
||||
warn!("Failed to read process [{}]: {}", pid, err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn self_pid_namespace_id() -> Option<u64> {
|
||||
let myself = match Process::myself() {
|
||||
Ok(proc) => proc,
|
||||
Err(err) => {
|
||||
warn!("Failed to read process information for self pid: {}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Self::read_pid_namespace_id(&myself)
|
||||
}
|
||||
|
||||
fn read_pid_namespace_id(proc: &Process) -> Option<u64> {
|
||||
let namespaces = match proc.namespaces() {
|
||||
Ok(namespaces) => namespaces,
|
||||
Err(err) => {
|
||||
warn!("Failed to read namespaces for process {}: {}", proc.pid, err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
for ns in namespaces {
|
||||
if ns.ns_type.as_os_str() == OsStr::new("pid") {
|
||||
return Some(ns.identifier);
|
||||
}
|
||||
}
|
||||
warn!("No pid namespace found for process {}", proc.pid);
|
||||
None
|
||||
}
|
||||
|
||||
pub fn lookup_pid_namespace(pid: libc::pid_t) -> Option<u64> {
|
||||
let proc = Self::read_process(pid)?;
|
||||
Self::read_pid_namespace_id(&proc)
|
||||
}
|
||||
|
||||
fn parent_process(proc: Process) -> Option<Process> {
|
||||
let ppid = proc.stat.ppid as libc::pid_t;
|
||||
if ppid == 1 {
|
||||
return None;
|
||||
}
|
||||
Self::read_process(ppid)
|
||||
}
|
||||
|
||||
pub fn lookup_pid(&self, pid: libc::pid_t) -> PidLookupResult {
|
||||
const MAX_PARENT_SEARCH: i32 = 8;
|
||||
let mut n = 0;
|
||||
|
||||
let mut proc = match Self::read_process(pid) {
|
||||
Some(proc) => proc,
|
||||
None => return PidLookupResult::Unknown,
|
||||
};
|
||||
|
||||
while n < MAX_PARENT_SEARCH {
|
||||
|
||||
let pid_ns_id = match Self::read_pid_namespace_id(&proc) {
|
||||
None => return PidLookupResult::Unknown,
|
||||
Some(ns_id) => ns_id,
|
||||
};
|
||||
|
||||
if Some(pid_ns_id) == self.my_pid_ns_id {
|
||||
return PidLookupResult::Citadel;
|
||||
}
|
||||
|
||||
if let Some(realm) = self.active_realms.iter()
|
||||
.find(|r| r.has_pid_ns(pid_ns_id))
|
||||
.cloned()
|
||||
{
|
||||
return PidLookupResult::Realm(realm)
|
||||
}
|
||||
|
||||
proc = match Self::parent_process(proc) {
|
||||
Some(proc) => proc,
|
||||
None => return PidLookupResult::Unknown,
|
||||
};
|
||||
|
||||
n += 1;
|
||||
}
|
||||
PidLookupResult::Unknown
|
||||
}
|
||||
|
||||
}
|
@ -12,6 +12,7 @@ use super::systemd::Systemd;
|
||||
|
||||
use crate::realmfs::Mountpoint;
|
||||
use crate::{symlink, util, Result, RealmFS, CommandLine, RealmManager, OverlayType};
|
||||
use crate::realm::pidmapper::PidMapper;
|
||||
|
||||
|
||||
const MAX_REALM_NAME_LEN:usize = 128;
|
||||
@ -37,20 +38,96 @@ impl RealmActiveState {
|
||||
|
||||
struct Inner {
|
||||
config: Arc<RealmConfig>,
|
||||
realm_name: String,
|
||||
timestamp: i64,
|
||||
leader_pid: Option<u32>,
|
||||
leader_pid_namespace: Option<u64>,
|
||||
active: RealmActiveState,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new(config: RealmConfig) -> Inner {
|
||||
fn new(config: RealmConfig, name: &str) -> Inner {
|
||||
Inner {
|
||||
config: Arc::new(config),
|
||||
realm_name: name.to_string(),
|
||||
timestamp: 0,
|
||||
leader_pid: None,
|
||||
leader_pid_namespace: None,
|
||||
active: RealmActiveState::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn reload_active_state(&mut self) {
|
||||
match Systemd::is_active(&self.realm_name) {
|
||||
Ok(active) => self.set_active(active),
|
||||
Err(err) => {
|
||||
warn!("Failed to run systemctl to determine realm is-active state: {}", err);
|
||||
self.set_active_state(RealmActiveState::Failed)
|
||||
},
|
||||
}
|
||||
}
|
||||
fn is_active(&mut self) -> bool {
|
||||
if self.active == RealmActiveState::Unknown {
|
||||
self.reload_active_state();
|
||||
}
|
||||
self.active == RealmActiveState::Active
|
||||
}
|
||||
|
||||
fn set_active(&mut self, is_active: bool) {
|
||||
let state = if is_active {
|
||||
RealmActiveState::Active
|
||||
} else {
|
||||
RealmActiveState::Inactive
|
||||
};
|
||||
self.set_active_state(state);
|
||||
}
|
||||
|
||||
fn set_active_state(&mut self, state: RealmActiveState) {
|
||||
if state != RealmActiveState::Active {
|
||||
self.leader_pid = None;
|
||||
self.leader_pid_namespace = None;
|
||||
}
|
||||
self.active = state;
|
||||
}
|
||||
|
||||
fn pid_ns(&mut self) -> Option<u64> {
|
||||
if !self.is_active() {
|
||||
return None;
|
||||
}
|
||||
if let Some(pid_ns) = self.leader_pid_namespace {
|
||||
return Some(pid_ns);
|
||||
}
|
||||
|
||||
let leader_pid = self.leader_pid()? as libc::pid_t;
|
||||
self.leader_pid_namespace = PidMapper::lookup_pid_namespace(leader_pid);
|
||||
self.leader_pid_namespace
|
||||
}
|
||||
|
||||
/// Query for 'leader pid' of realm nspawn instance with machinectl.
|
||||
/// The leader pid is the 'pid 1' of the realm container as seen from
|
||||
/// outside the PID namespace.
|
||||
fn leader_pid(&mut self) -> Option<u32> {
|
||||
if !self.is_active() {
|
||||
return None;
|
||||
}
|
||||
if let Some(pid) = self.leader_pid {
|
||||
return Some(pid);
|
||||
}
|
||||
|
||||
match self.query_leader_pid() {
|
||||
Ok(pid) => self.leader_pid = Some(pid),
|
||||
Err(e) => warn!("error retrieving leader pid for realm: {}", e)
|
||||
}
|
||||
self.leader_pid
|
||||
}
|
||||
|
||||
fn query_leader_pid(&self) -> Result<u32> {
|
||||
let output = cmd_with_output!("/usr/bin/machinectl", "show --value {} -p Leader", self.realm_name)?;
|
||||
let pid = output.parse::<u32>()
|
||||
.map_err(|_| format_err!("Failed to parse leader pid output from machinectl: {}", output))?;
|
||||
Ok(pid)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -65,7 +142,7 @@ impl Realm {
|
||||
|
||||
pub(crate) fn new(name: &str) -> Realm {
|
||||
let config = RealmConfig::unloaded_realm_config(name);
|
||||
let inner = Inner::new(config);
|
||||
let inner = Inner::new(config, name);
|
||||
let inner = Arc::new(RwLock::new(inner));
|
||||
let name = Arc::new(name.to_string());
|
||||
let manager = Weak::new();
|
||||
@ -93,45 +170,19 @@ impl Realm {
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
if self.inner().active == RealmActiveState::Unknown {
|
||||
self.reload_active_state();
|
||||
}
|
||||
self.inner().active == RealmActiveState::Active
|
||||
self.inner_mut().is_active()
|
||||
}
|
||||
|
||||
pub fn set_active(&self, is_active: bool) {
|
||||
let state = if is_active {
|
||||
RealmActiveState::Active
|
||||
} else {
|
||||
RealmActiveState::Inactive
|
||||
};
|
||||
self.set_active_state(state);
|
||||
self.inner_mut().set_active(is_active)
|
||||
}
|
||||
|
||||
pub fn is_system(&self) -> bool {
|
||||
self.config().system_realm()
|
||||
}
|
||||
|
||||
fn set_active_state(&self, state: RealmActiveState) {
|
||||
let mut inner = self.inner_mut();
|
||||
if state != RealmActiveState::Active {
|
||||
inner.leader_pid = None;
|
||||
}
|
||||
inner.active = state;
|
||||
}
|
||||
|
||||
fn reload_active_state(&self) {
|
||||
match Systemd::is_active(self) {
|
||||
Ok(active) => self.set_active(active),
|
||||
Err(err) => {
|
||||
warn!("Failed to run systemctl to determine realm is-active state: {}", err);
|
||||
self.set_active_state(RealmActiveState::Failed)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_active_from_systemctl(&self, output: &str) {
|
||||
self.set_active_state(RealmActiveState::from_sysctl_output(output));
|
||||
self.inner_mut().set_active_state(RealmActiveState::from_sysctl_output(output));
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
@ -389,6 +440,17 @@ impl Realm {
|
||||
self.leader_pid().map(|pid| PathBuf::from(format!("/proc/{}/root", pid)))
|
||||
}
|
||||
|
||||
pub fn pid_ns(&self) -> Option<u64> {
|
||||
self.inner_mut().pid_ns()
|
||||
}
|
||||
|
||||
pub fn has_pid_ns(&self, ns_id: u64) -> bool {
|
||||
self.inner_mut()
|
||||
.pid_ns()
|
||||
.map(|id| id == ns_id)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn pid_namespace(&self) -> Option<String> {
|
||||
let pid = self.leader_pid()?;
|
||||
let path = symlink::read(format!("/proc/{}/ns/pid", pid))?;
|
||||
@ -399,27 +461,7 @@ impl Realm {
|
||||
/// The leader pid is the 'pid 1' of the realm container as seen from
|
||||
/// outside the PID namespace.
|
||||
pub fn leader_pid(&self) -> Option<u32> {
|
||||
if !self.is_active() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut lock = self.inner_mut();
|
||||
|
||||
if let Some(pid) = lock.leader_pid {
|
||||
return Some(pid);
|
||||
}
|
||||
match self.query_leader_pid() {
|
||||
Ok(pid) => lock.leader_pid = Some(pid),
|
||||
Err(e) => warn!("error retrieving leader pid for realm: {}", e)
|
||||
}
|
||||
lock.leader_pid
|
||||
}
|
||||
|
||||
fn query_leader_pid(&self) -> Result<u32> {
|
||||
let output = cmd_with_output!("/usr/bin/machinectl", "show --value {} -p Leader", self.name())?;
|
||||
let pid = output.parse::<u32>()
|
||||
.map_err(|_| format_err!("Failed to parse leader pid output from machinectl: {}", output))?;
|
||||
Ok(pid)
|
||||
self.inner_mut().leader_pid()
|
||||
}
|
||||
|
||||
/// Return `true` if `name` is a valid name for a realm.
|
||||
|
@ -115,10 +115,10 @@ impl Systemd {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_active(realm: &Realm) -> Result<bool> {
|
||||
pub fn is_active(realm_name: &str) -> Result<bool> {
|
||||
let ok = Command::new(SYSTEMCTL_PATH)
|
||||
.args(&["--quiet", "is-active"])
|
||||
.arg(format!("realm-{}", realm.name()))
|
||||
.arg(format!("realm-{}", realm_name))
|
||||
.status()
|
||||
.map(|status| status.success())
|
||||
.map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::process::{Command,Stdio};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::os::unix::fs as unixfs;
|
||||
use std::env;
|
||||
use std::fs::{self, File, DirEntry};
|
||||
@ -163,6 +163,13 @@ pub fn chown(path: &Path, uid: u32, gid: u32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn chmod(path: &Path, mode: u32) -> Result<()> {
|
||||
let meta = path.metadata()
|
||||
.map_err(context!("Failed to read metadata from path {:?}", path))?;
|
||||
meta.permissions().set_mode(mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rename or move file at `from` to file path `to`
|
||||
///
|
||||
/// A wrapper around `fs::rename()` which on failure returns an error indicating the source and
|
||||
|
Loading…
Reference in New Issue
Block a user