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::realmfs::resizer::ResizeSize;
|
||||||
pub use crate::realm::overlay::RealmOverlay;
|
pub use crate::realm::overlay::RealmOverlay;
|
||||||
pub use crate::realm::realm::Realm;
|
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::config::{RealmConfig,OverlayType,GLOBAL_CONFIG};
|
||||||
pub use crate::realm::events::RealmEvent;
|
pub use crate::realm::events::RealmEvent;
|
||||||
pub use crate::realm::realms::Realms;
|
pub use crate::realm::realms::Realms;
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use posix_acl::{ACL_EXECUTE, ACL_READ, PosixACL, Qualifier};
|
use posix_acl::{ACL_EXECUTE, ACL_READ, PosixACL, Qualifier};
|
||||||
|
|
||||||
use crate::{Mountpoint, Result, Realms, RealmFS, Realm, util};
|
use crate::{Mountpoint, Result, Realms, RealmFS, Realm, util};
|
||||||
|
use crate::realm::pidmapper::{PidLookupResult, PidMapper};
|
||||||
use crate::realmfs::realmfs_set::RealmFSSet;
|
use crate::realmfs::realmfs_set::RealmFSSet;
|
||||||
|
|
||||||
use super::systemd::Systemd;
|
use super::systemd::Systemd;
|
||||||
@ -333,27 +334,9 @@ impl RealmManager {
|
|||||||
self.inner().realms.by_name(name)
|
self.inner().realms.by_name(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn realm_by_pid(&self, pid: u32) -> Option<Realm> {
|
pub fn realm_by_pid(&self, pid: u32) -> PidLookupResult {
|
||||||
match Self::read_realm_name_by_pid(pid) {
|
let mapper = PidMapper::new(self.active_realms(false));
|
||||||
Ok(name) => self.realm_by_name(name.as_str()),
|
mapper.lookup_pid(pid as libc::pid_t)
|
||||||
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 rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
|
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 network;
|
||||||
pub(crate) mod create;
|
pub(crate) mod create;
|
||||||
pub(crate) mod events;
|
pub(crate) mod events;
|
||||||
|
pub(crate) mod pidmapper;
|
||||||
mod systemd;
|
mod systemd;
|
||||||
mod launcher;
|
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::realmfs::Mountpoint;
|
||||||
use crate::{symlink, util, Result, RealmFS, CommandLine, RealmManager, OverlayType};
|
use crate::{symlink, util, Result, RealmFS, CommandLine, RealmManager, OverlayType};
|
||||||
|
use crate::realm::pidmapper::PidMapper;
|
||||||
|
|
||||||
|
|
||||||
const MAX_REALM_NAME_LEN:usize = 128;
|
const MAX_REALM_NAME_LEN:usize = 128;
|
||||||
@ -37,20 +38,96 @@ impl RealmActiveState {
|
|||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
config: Arc<RealmConfig>,
|
config: Arc<RealmConfig>,
|
||||||
|
realm_name: String,
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
leader_pid: Option<u32>,
|
leader_pid: Option<u32>,
|
||||||
|
leader_pid_namespace: Option<u64>,
|
||||||
active: RealmActiveState,
|
active: RealmActiveState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inner {
|
impl Inner {
|
||||||
fn new(config: RealmConfig) -> Inner {
|
fn new(config: RealmConfig, name: &str) -> Inner {
|
||||||
Inner {
|
Inner {
|
||||||
config: Arc::new(config),
|
config: Arc::new(config),
|
||||||
|
realm_name: name.to_string(),
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
leader_pid: None,
|
leader_pid: None,
|
||||||
|
leader_pid_namespace: None,
|
||||||
active: RealmActiveState::Unknown,
|
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)]
|
#[derive(Clone)]
|
||||||
@ -65,7 +142,7 @@ impl Realm {
|
|||||||
|
|
||||||
pub(crate) fn new(name: &str) -> Realm {
|
pub(crate) fn new(name: &str) -> Realm {
|
||||||
let config = RealmConfig::unloaded_realm_config(name);
|
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 inner = Arc::new(RwLock::new(inner));
|
||||||
let name = Arc::new(name.to_string());
|
let name = Arc::new(name.to_string());
|
||||||
let manager = Weak::new();
|
let manager = Weak::new();
|
||||||
@ -93,45 +170,19 @@ impl Realm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_active(&self) -> bool {
|
pub fn is_active(&self) -> bool {
|
||||||
if self.inner().active == RealmActiveState::Unknown {
|
self.inner_mut().is_active()
|
||||||
self.reload_active_state();
|
|
||||||
}
|
|
||||||
self.inner().active == RealmActiveState::Active
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_active(&self, is_active: bool) {
|
pub fn set_active(&self, is_active: bool) {
|
||||||
let state = if is_active {
|
self.inner_mut().set_active(is_active)
|
||||||
RealmActiveState::Active
|
|
||||||
} else {
|
|
||||||
RealmActiveState::Inactive
|
|
||||||
};
|
|
||||||
self.set_active_state(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_system(&self) -> bool {
|
pub fn is_system(&self) -> bool {
|
||||||
self.config().system_realm()
|
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) {
|
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 {
|
pub fn name(&self) -> &str {
|
||||||
@ -389,6 +440,17 @@ impl Realm {
|
|||||||
self.leader_pid().map(|pid| PathBuf::from(format!("/proc/{}/root", pid)))
|
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> {
|
pub fn pid_namespace(&self) -> Option<String> {
|
||||||
let pid = self.leader_pid()?;
|
let pid = self.leader_pid()?;
|
||||||
let path = symlink::read(format!("/proc/{}/ns/pid", 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
|
/// The leader pid is the 'pid 1' of the realm container as seen from
|
||||||
/// outside the PID namespace.
|
/// outside the PID namespace.
|
||||||
pub fn leader_pid(&self) -> Option<u32> {
|
pub fn leader_pid(&self) -> Option<u32> {
|
||||||
if !self.is_active() {
|
self.inner_mut().leader_pid()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if `name` is a valid name for a realm.
|
/// Return `true` if `name` is a valid name for a realm.
|
||||||
|
@ -115,10 +115,10 @@ impl Systemd {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_active(realm: &Realm) -> Result<bool> {
|
pub fn is_active(realm_name: &str) -> Result<bool> {
|
||||||
let ok = Command::new(SYSTEMCTL_PATH)
|
let ok = Command::new(SYSTEMCTL_PATH)
|
||||||
.args(&["--quiet", "is-active"])
|
.args(&["--quiet", "is-active"])
|
||||||
.arg(format!("realm-{}", realm.name()))
|
.arg(format!("realm-{}", realm_name))
|
||||||
.status()
|
.status()
|
||||||
.map(|status| status.success())
|
.map(|status| status.success())
|
||||||
.map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?;
|
.map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
use std::process::{Command,Stdio};
|
use std::process::{Command,Stdio};
|
||||||
use std::os::unix::ffi::OsStrExt;
|
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::os::unix::fs as unixfs;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{self, File, DirEntry};
|
use std::fs::{self, File, DirEntry};
|
||||||
@ -163,6 +163,13 @@ pub fn chown(path: &Path, uid: u32, gid: u32) -> Result<()> {
|
|||||||
Ok(())
|
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`
|
/// 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
|
/// A wrapper around `fs::rename()` which on failure returns an error indicating the source and
|
||||||
|
Loading…
Reference in New Issue
Block a user