use crate::{
backend::session::{AsErrno, Session, Signal as SessionSignal},
utils::signaling::Signaler,
};
use dbus::{
arg::{messageitem::MessageItem, OwnedFd},
strings::{BusName, Interface, Member, Path as DbusPath},
Message,
};
use nix::{
fcntl::OFlag,
sys::stat::{fstat, major, minor, stat},
};
use std::{
cell::RefCell,
fmt,
io::Error as IoError,
os::unix::io::RawFd,
path::Path,
rc::{Rc, Weak},
sync::atomic::{AtomicBool, Ordering},
};
use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
use slog::{debug, error, info, o, warn};
use super::DBusConnection;
struct LogindSessionImpl {
session_id: String,
conn: RefCell<DBusConnection>,
session_path: DbusPath<'static>,
active: AtomicBool,
signaler: Signaler<SessionSignal>,
seat: String,
logger: ::slog::Logger,
}
impl fmt::Debug for LogindSessionImpl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LogindSessionImpl")
.field("session_id", &self.session_id)
.field("conn", &"...")
.field("session_path", &self.session_path)
.field("active", &self.active)
.field("signaler", &self.signaler)
.field("seat", &self.seat)
.field("logger", &self.logger)
.finish()
}
}
#[derive(Debug, Clone)]
pub struct LogindSession {
internal: Weak<LogindSessionImpl>,
seat: String,
}
#[derive(Debug, Clone)]
pub struct LogindSessionNotifier {
internal: Rc<LogindSessionImpl>,
}
impl LogindSession {
pub fn new<L>(logger: L) -> Result<(LogindSession, LogindSessionNotifier), Error>
where
L: Into<Option<::slog::Logger>>,
{
let logger = crate::slog_or_fallback(logger)
.new(o!("smithay_module" => "backend_session", "session_type" => "logind"));
let (session_id, seat, vt) = ffi::get_login_state()?;
let conn = DBusConnection::new_system().map_err(Error::FailedDbusConnection)?;
let session_path = LogindSessionImpl::blocking_call(
&conn,
"org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"GetSession",
Some(vec![session_id.clone().into()]),
)?
.get1::<DbusPath<'static>>()
.ok_or(Error::UnexpectedMethodReturn)?;
let match1 = String::from(
"type='signal',\
sender='org.freedesktop.login1',\
interface='org.freedesktop.login1.Manager',\
member='SessionRemoved',\
path='/org/freedesktop/login1'",
);
conn.add_match(&match1)
.map_err(|source| Error::DbusMatchFailed(match1, source))?;
let match2 = format!(
"type='signal',\
sender='org.freedesktop.login1',\
interface='org.freedesktop.login1.Session',\
member='PauseDevice',\
path='{}'",
&session_path
);
conn.add_match(&match2)
.map_err(|source| Error::DbusMatchFailed(match2, source))?;
let match3 = format!(
"type='signal',\
sender='org.freedesktop.login1',\
interface='org.freedesktop.login1.Session',\
member='ResumeDevice',\
path='{}'",
&session_path
);
conn.add_match(&match3)
.map_err(|source| Error::DbusMatchFailed(match3, source))?;
let match4 = format!(
"type='signal',\
sender='org.freedesktop.login1',\
interface='org.freedesktop.DBus.Properties',\
member='PropertiesChanged',\
path='{}'",
&session_path
);
conn.add_match(&match4)
.map_err(|source| Error::DbusMatchFailed(match4, source))?;
LogindSessionImpl::blocking_call(
&conn,
"org.freedesktop.login1",
session_path.clone(),
"org.freedesktop.login1.Session",
"Activate",
None,
)?;
LogindSessionImpl::blocking_call(
&conn,
"org.freedesktop.login1",
session_path.clone(),
"org.freedesktop.login1.Session",
"TakeControl",
Some(vec![false.into()]),
)?;
let conn = RefCell::new(conn);
let internal = Rc::new(LogindSessionImpl {
session_id: session_id.clone(),
conn,
session_path,
active: AtomicBool::new(true),
signaler: Signaler::new(),
seat: seat.clone(),
logger: logger.new(o!("id" => session_id, "seat" => seat.clone(), "vt" => format!("{:?}", &vt))),
});
Ok((
LogindSession {
internal: Rc::downgrade(&internal),
seat,
},
LogindSessionNotifier { internal },
))
}
}
impl LogindSessionNotifier {
pub fn session(&self) -> LogindSession {
LogindSession {
internal: Rc::downgrade(&self.internal),
seat: self.internal.seat.clone(),
}
}
pub fn signaler(&self) -> Signaler<SessionSignal> {
self.internal.signaler.clone()
}
}
impl LogindSessionImpl {
fn blocking_call<'d, 'p, 'i, 'm, D, P, I, M>(
conn: &DBusConnection,
destination: D,
path: P,
interface: I,
method: M,
arguments: Option<Vec<MessageItem>>,
) -> Result<Message, Error>
where
D: Into<BusName<'d>>,
P: Into<DbusPath<'p>>,
I: Into<Interface<'i>>,
M: Into<Member<'m>>,
{
let destination = destination.into().into_static();
let path = path.into().into_static();
let interface = interface.into().into_static();
let method = method.into().into_static();
let mut message = Message::method_call(&destination, &path, &interface, &method);
if let Some(arguments) = arguments {
message.append_items(&arguments)
};
let mut message = conn
.channel()
.send_with_reply_and_block(message, std::time::Duration::from_millis(1000))
.map_err(|source| Error::FailedToSendDbusCall {
bus: destination.clone(),
path: path.clone(),
interface: interface.clone(),
member: method.clone(),
source,
})?;
match message.as_result() {
Ok(_) => Ok(message),
Err(err) => Err(Error::DbusCallFailed {
bus: destination.clone(),
path: path.clone(),
interface: interface.clone(),
member: method.clone(),
source: err,
}),
}
}
fn handle_message(&self, message: dbus::Message) -> Result<(), Error> {
if &*message.interface().unwrap() == "org.freedesktop.login1.Manager"
&& &*message.member().unwrap() == "SessionRemoved"
&& message.get1::<String>().unwrap() == self.session_id
{
error!(self.logger, "Session got closed by logind");
self.signaler.signal(SessionSignal::PauseSession);
self.active.store(false, Ordering::SeqCst);
warn!(self.logger, "Session is now considered inactive");
} else if &*message.interface().unwrap() == "org.freedesktop.login1.Session" {
if &*message.member().unwrap() == "PauseDevice" {
let (major, minor, pause_type) = message.get3::<u32, u32, String>();
let major = major.ok_or(Error::UnexpectedMethodReturn)?;
let minor = minor.ok_or(Error::UnexpectedMethodReturn)?;
let pause_type = pause_type.ok_or(Error::UnexpectedMethodReturn)?;
debug!(
self.logger,
"Request of type \"{}\" to close device ({},{})", pause_type, major, minor
);
if pause_type != "gone" {
self.signaler.signal(SessionSignal::PauseDevice { major, minor });
}
if pause_type == "pause" {
LogindSessionImpl::blocking_call(
&*self.conn.borrow(),
"org.freedesktop.login1",
self.session_path.clone(),
"org.freedesktop.login1.Session",
"PauseDeviceComplete",
Some(vec![major.into(), minor.into()]),
)?;
}
} else if &*message.member().unwrap() == "ResumeDevice" {
let (major, minor, fd) = message.get3::<u32, u32, OwnedFd>();
let major = major.ok_or(Error::UnexpectedMethodReturn)?;
let minor = minor.ok_or(Error::UnexpectedMethodReturn)?;
let fd = fd.ok_or(Error::UnexpectedMethodReturn)?.into_fd();
debug!(self.logger, "Reactivating device ({},{})", major, minor);
self.signaler.signal(SessionSignal::ActivateDevice {
major,
minor,
new_fd: Some(fd),
});
}
} else if &*message.interface().unwrap() == "org.freedesktop.DBus.Properties"
&& &*message.member().unwrap() == "PropertiesChanged"
{
use dbus::arg::{Array, Dict, Get, Iter, Variant};
let (_, changed, _) = message
.get3::<String, Dict<'_, String, Variant<Iter<'_>>, Iter<'_>>, Array<'_, String, Iter<'_>>>();
let mut changed = changed.ok_or(Error::UnexpectedMethodReturn)?;
if let Some((_, mut value)) = changed.find(|&(ref key, _)| &*key == "Active") {
if let Some(active) = Get::get(&mut value.0) {
self.active.store(active, Ordering::SeqCst);
}
}
} else {
if let Some(reply) = dbus::channel::default_reply(&message) {
self.conn
.borrow()
.channel()
.send(reply)
.map_err(|()| Error::SessionLost)?;
}
}
Ok(())
}
}
impl Session for LogindSession {
type Error = Error;
fn open(&mut self, path: &Path, _flags: OFlag) -> Result<RawFd, Error> {
if let Some(session) = self.internal.upgrade() {
let stat = stat(path).map_err(Error::FailedToStatDevice)?;
let (fd, _paused) = LogindSessionImpl::blocking_call(
&*session.conn.borrow(),
"org.freedesktop.login1",
session.session_path.clone(),
"org.freedesktop.login1.Session",
"TakeDevice",
Some(vec![
(major(stat.st_rdev) as u32).into(),
(minor(stat.st_rdev) as u32).into(),
]),
)?
.get2::<OwnedFd, bool>();
let fd = fd.ok_or(Error::UnexpectedMethodReturn)?.into_fd();
Ok(fd)
} else {
Err(Error::SessionLost)
}
}
fn close(&mut self, fd: RawFd) -> Result<(), Error> {
if let Some(session) = self.internal.upgrade() {
let stat = fstat(fd).map_err(Error::FailedToStatDevice)?;
LogindSessionImpl::blocking_call(
&*session.conn.borrow(),
"org.freedesktop.login1",
session.session_path.clone(),
"org.freedesktop.login1.Session",
"ReleaseDevice",
Some(vec![
(major(stat.st_rdev) as u32).into(),
(minor(stat.st_rdev) as u32).into(),
]),
)
.map(|_| ())
} else {
Err(Error::SessionLost)
}
}
fn is_active(&self) -> bool {
if let Some(internal) = self.internal.upgrade() {
internal.active.load(Ordering::SeqCst)
} else {
false
}
}
fn seat(&self) -> String {
self.seat.clone()
}
fn change_vt(&mut self, vt_num: i32) -> Result<(), Error> {
if let Some(session) = self.internal.upgrade() {
LogindSessionImpl::blocking_call(
&*session.conn.borrow_mut(),
"org.freedesktop.login1",
"/org/freedesktop/login1/seat/self",
"org.freedesktop.login1.Seat",
"SwitchTo",
Some(vec![(vt_num as u32).into()]),
)
.map(|_| ())
} else {
Err(Error::SessionLost)
}
}
}
impl Drop for LogindSessionNotifier {
fn drop(&mut self) {
info!(self.internal.logger, "Closing logind session");
let _ = LogindSessionImpl::blocking_call(
&*self.internal.conn.borrow(),
"org.freedesktop.login1",
self.internal.session_path.clone(),
"org.freedesktop.login1.Session",
"ReleaseControl",
None,
);
}
}
impl EventSource for LogindSessionNotifier {
type Event = ();
type Metadata = ();
type Ret = ();
fn process_events<F>(&mut self, readiness: Readiness, token: Token, _: F) -> std::io::Result<PostAction>
where
F: FnMut((), &mut ()),
{
let mut messages = Vec::new();
self.internal
.conn
.borrow_mut()
.process_events(readiness, token, |msg, _| messages.push(msg))?;
for msg in messages {
if let Err(err) = self.internal.handle_message(msg) {
error!(self.internal.logger, "Error handling dbus messages: {}", err);
}
}
Ok(PostAction::Continue)
}
fn register(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> std::io::Result<()> {
self.internal.conn.borrow_mut().register(poll, factory)
}
fn reregister(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> std::io::Result<()> {
self.internal.conn.borrow_mut().reregister(poll, factory)
}
fn unregister(&mut self, poll: &mut Poll) -> std::io::Result<()> {
self.internal.conn.borrow_mut().unregister(poll)
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Failed to connect to dbus system socket")]
FailedDbusConnection(#[source] dbus::Error),
#[error("Failed to get session from logind")]
FailedToGetSession(#[source] IoError),
#[error("Failed to get seat from logind")]
FailedToGetSeat(#[source] IoError),
#[error("Failed to get vt from logind")]
FailedToGetVT,
#[error("Failed to call dbus method for service: {bus:?}, path: {path:?}, interface: {interface:?}, member: {member:?}")]
FailedToSendDbusCall {
bus: BusName<'static>,
path: DbusPath<'static>,
interface: Interface<'static>,
member: Member<'static>,
#[source]
source: dbus::Error,
},
#[error("Dbus message call failed for service: {bus:?}, path: {path:?}, interface: {interface:?}, member: {member:?}")]
DbusCallFailed {
bus: BusName<'static>,
path: DbusPath<'static>,
interface: Interface<'static>,
member: Member<'static>,
#[source]
source: dbus::Error,
},
#[error("Dbus method return had unexpected format")]
UnexpectedMethodReturn,
#[error("Failed to setup dbus match rule {0}")]
DbusMatchFailed(String, #[source] dbus::Error),
#[error("Failed to stat device")]
FailedToStatDevice(#[source] nix::Error),
#[error("Session is already closed")]
SessionLost,
}
impl AsErrno for Error {
fn as_errno(&self) -> Option<i32> {
None
}
}
mod ffi {
use libc::pid_t;
use std::{
ffi::CString,
os::raw::{c_char, c_int, c_uint},
};
pub fn get_login_state() -> Result<(String, String, Option<u32>), super::Error> {
let session_name = unsafe {
let mut session_name: *mut c_char = std::ptr::null_mut();
let ret = sd_pid_get_session(0, &mut session_name);
if ret < 0 {
return Err(super::Error::FailedToGetSession(
std::io::Error::from_raw_os_error(-ret),
));
}
CString::from_raw(session_name)
};
let seat_name = unsafe {
let mut seat_name: *mut c_char = std::ptr::null_mut();
let ret = sd_session_get_seat(session_name.as_ptr(), &mut seat_name);
if ret < 0 {
return Err(super::Error::FailedToGetSeat(std::io::Error::from_raw_os_error(
-ret,
)));
}
CString::from_raw(seat_name)
};
let vt_num = unsafe {
let mut vt_num = 0;
let ret = sd_session_get_vt(session_name.as_ptr(), &mut vt_num);
if ret < 0 {
None
} else {
Some(vt_num)
}
};
Ok((
session_name.into_string().unwrap(),
seat_name.into_string().unwrap(),
vt_num,
))
}
extern "C" {
fn sd_pid_get_session(pid: pid_t, out_session: *mut *mut c_char) -> c_int;
fn sd_session_get_seat(session: *const c_char, out_seat: *mut *mut c_char) -> c_int;
fn sd_session_get_vt(session: *const c_char, out_vt: *mut c_uint) -> c_int;
}
}