diff --git a/greetd/src/config.rs b/greetd/src/config.rs deleted file mode 100644 index db72589..0000000 --- a/greetd/src/config.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::{env, fs::read_to_string}; - -use getopts::Options; -use serde::Deserialize; - -fn default_vt() -> toml::Value { - toml::Value::String("next".to_string()) -} - -fn default_greeter_user() -> String { - "greeter".to_string() -} - -fn default_socket_path() -> String { - "/run/greetd.sock".to_string() -} - -#[derive(Debug, Deserialize)] -pub struct Config { - #[serde(default = "default_vt")] - pub vt: toml::Value, - pub greeter: String, - #[serde(default = "default_greeter_user")] - pub greeter_user: String, - - #[serde(default = "default_socket_path")] - pub socket_path: String, - - #[serde(skip_deserializing)] - pub session_worker: usize, -} - -pub enum VtSelection { - Next, - Current, - None, - Specific(usize), -} - -impl Config { - pub fn vt(&self) -> VtSelection { - match &self.vt { - toml::Value::String(s) => match s.as_str() { - "next" => VtSelection::Next, - "current" => VtSelection::Current, - "none" => VtSelection::None, - _ => panic!("unknown value of vt, expect next, current, none, or vt number"), - }, - toml::Value::Integer(u) => VtSelection::Specific(*u as usize), - _ => panic!("unknown value of vt, expect next, current, or vt number"), - } - } -} - -fn print_usage(program: &str, opts: Options) { - let brief = format!("Usage: {} [options]", program); - print!("{}", opts.usage(&brief)); -} - -pub fn read_config() -> Config { - let args: Vec = env::args().collect(); - let program = args[0].clone(); - let mut opts = Options::new(); - opts.optflag("h", "help", "print this help menu"); - opts.optopt("t", "vt", "VT to run on", "VT"); - opts.optopt("s", "socket-path", "socket path to use", "SOCKET_PATH"); - opts.optopt("g", "greeter", "greeter to run", "GREETER"); - opts.optopt("u", "greeter-user", "user to run greeter as", "USER"); - opts.optopt("c", "config", "config file to use", "CONFIG_FILE"); - opts.optopt( - "w", - "session-worker", - "start a session worker (internal)", - "FD", - ); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => panic!(f.to_string()), - }; - if matches.opt_present("h") { - print_usage(&program, opts); - std::process::exit(0); - } - - let mut config = match read_to_string( - matches - .opt_str("config") - .unwrap_or_else(|| "/etc/greetd/config.toml".to_string()), - ) { - Ok(s) => match toml::from_str(&s) { - Ok(v) => v, - Err(e) => { - eprintln!("Unable to parse configuration file: {:?}", e); - eprintln!("Please fix the configuration file and try again."); - std::process::exit(1); - } - }, - Err(_) => Config { - vt: default_vt(), - greeter: "".to_string(), - greeter_user: "".to_string(), - socket_path: default_socket_path(), - session_worker: 0, - }, - }; - - if let Some(vt) = matches.opt_str("vt") { - config.vt = match vt.as_str() { - "next" => toml::Value::String("next".to_string()), - "current" => toml::Value::String("current".to_string()), - "none" => toml::Value::String("none".to_string()), - v => toml::Value::Integer(v.parse().expect("could not parse vt number")), - } - } - if let Some(greeter) = matches.opt_str("greeter") { - config.greeter = greeter; - } - if let Some(user) = matches.opt_str("greeter-user") { - config.greeter_user = user; - } - if let Some(socket_path) = matches.opt_str("socket-path") { - config.socket_path = socket_path; - } - if let Some(session_worker) = matches - .opt_get("session-worker") - .expect("unable to parse session-worker") - { - config.session_worker = session_worker - } - - if config.greeter.is_empty() { - eprintln!("No greeter specified. Run with --help for more information."); - std::process::exit(1); - } - if config.greeter_user.is_empty() { - eprintln!("No greeter user specified. Run with --help for more information."); - std::process::exit(1); - } - - config -} diff --git a/greetd/src/config/defaults.rs b/greetd/src/config/defaults.rs new file mode 100644 index 0000000..e77ecee --- /dev/null +++ b/greetd/src/config/defaults.rs @@ -0,0 +1,9 @@ +use super::vtselection::VtSelection; + +pub fn default_vt() -> VtSelection { + VtSelection::Next +} + +pub fn default_greeter_user() -> String { + "greeter".to_string() +} diff --git a/greetd/src/config/mod.rs b/greetd/src/config/mod.rs new file mode 100644 index 0000000..9df28fe --- /dev/null +++ b/greetd/src/config/mod.rs @@ -0,0 +1,93 @@ +mod defaults; +mod old_config; +mod types; +mod vtselection; + +use std::{env, fs::read_to_string}; + +use getopts::Options; + +use super::error::Error; +use old_config::*; + +pub use types::*; +pub use vtselection::VtSelection; + +fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {} [options]", program); + print!("{}", opts.usage(&brief)); +} + +pub fn read_config() -> Result { + let args: Vec = env::args().collect(); + let program = args[0].clone(); + let mut opts = Options::new(); + opts.optflag("h", "help", "print this help menu"); + opts.optopt("s", "socket-path", "socket path to use", "SOCKET_PATH"); + opts.optopt("c", "config", "config file to use", "CONFIG_FILE"); + opts.optopt( + "w", + "session-worker", + "start a session worker (internal)", + "FD", + ); + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => return Err(format!("could not parse arguments: {}", f).into()), + }; + if matches.opt_present("h") { + print_usage(&program, opts); + std::process::exit(0); + } + + let internal = ConfigInternal { + socket_path: matches + .opt_str("socket-path") + .unwrap_or_else(|| "/run/greetd.sock".to_string()), + session_worker: matches + .opt_get("session-worker") + .expect("unable to parse session-worker") + .unwrap_or(0), + }; + + let file = match read_to_string( + matches + .opt_str("config") + .unwrap_or_else(|| "/etc/greetd/config.toml".to_string()), + ) { + Ok(s) => match toml::from_str(&s) { + Ok(v) => v, + Err(e) => match try_read_old_config(&s) { + Ok(v) => { + eprintln!("warning: Fallback to old config format, caused by : {}", e); + v + } + Err(_e) => { + return Err(Error::ConfigError( + format!("unable to parse configuration file: {}", e).to_string(), + )) + } + }, + }, + Err(_) => ConfigFile { + default_session: ConfigDefaultSession { + user: "greeter".to_string(), + command: "".to_string(), + }, + terminal: Default::default(), + }, + }; + + if file.default_session.command.is_empty() { + return Err(Error::ConfigError( + "no default session user specified".to_string(), + )); + } + if file.default_session.user.is_empty() { + return Err(Error::ConfigError( + "no default session user specified".to_string(), + )); + } + + Ok(Config { file, internal }) +} diff --git a/greetd/src/config/old_config.rs b/greetd/src/config/old_config.rs new file mode 100644 index 0000000..8a32b31 --- /dev/null +++ b/greetd/src/config/old_config.rs @@ -0,0 +1,35 @@ +use serde::Deserialize; + +use crate::error::Error; + +use super::vtselection::VtSelection; + +use super::{defaults::*, types::*}; + +#[derive(Debug, Deserialize)] +pub struct OldConfig { + #[serde(default = "default_vt")] + pub vt: VtSelection, + pub greeter: String, + #[serde(default = "default_greeter_user")] + pub greeter_user: String, +} + +pub fn try_read_old_config(s: &str) -> Result { + let oldconfig: OldConfig = match toml::from_str(&s) { + Ok(v) => v, + Err(e) => { + return Err(Error::ConfigError( + format!("unable to parse configuration file: {}", e).to_string(), + )) + } + }; + + Ok(ConfigFile { + terminal: ConfigTerminal { vt: oldconfig.vt }, + default_session: ConfigDefaultSession { + user: oldconfig.greeter_user, + command: oldconfig.greeter, + }, + }) +} diff --git a/greetd/src/config/types.rs b/greetd/src/config/types.rs new file mode 100644 index 0000000..bbb77bf --- /dev/null +++ b/greetd/src/config/types.rs @@ -0,0 +1,37 @@ +use super::{defaults::*, vtselection::VtSelection}; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct ConfigDefaultSession { + pub command: String, + #[serde(default = "default_greeter_user")] + pub user: String, +} + +pub struct ConfigInternal { + pub socket_path: String, + pub session_worker: usize, +} + +#[derive(Debug, Deserialize)] +pub struct ConfigTerminal { + #[serde(default = "default_vt")] + pub vt: VtSelection, +} + +impl Default for ConfigTerminal { + fn default() -> ConfigTerminal { + ConfigTerminal { vt: default_vt() } + } +} + +#[derive(Debug, Deserialize)] +pub struct ConfigFile { + pub terminal: ConfigTerminal, + pub default_session: ConfigDefaultSession, +} + +pub struct Config { + pub file: ConfigFile, + pub internal: ConfigInternal, +} diff --git a/greetd/src/config/vtselection.rs b/greetd/src/config/vtselection.rs new file mode 100644 index 0000000..e9cfa2d --- /dev/null +++ b/greetd/src/config/vtselection.rs @@ -0,0 +1,62 @@ +use std::fmt; + +use serde::{ + de::{self, Deserializer, Visitor}, + Deserialize, +}; + +struct VtSelectionVisitor; + +impl<'de> Visitor<'de> for VtSelectionVisitor { + type Value = VtSelection; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("next, currrent, none or a positive vt number") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + if value < 1 { + Err(de::Error::invalid_value( + de::Unexpected::Signed(value), + &"next, current, none or a positive vt number", + )) + } else { + Ok(VtSelection::Specific(value as usize)) + } + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "next" => Ok(VtSelection::Next), + "current" => Ok(VtSelection::Current), + "none" => Ok(VtSelection::None), + _ => Err(de::Error::invalid_value( + de::Unexpected::Str(value), + &"next, current, none or a postive vt number", + )), + } + } +} + +#[derive(Debug)] +pub enum VtSelection { + Next, + Current, + None, + Specific(usize), +} + +impl<'de> Deserialize<'de> for VtSelection { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(VtSelectionVisitor) + } +} diff --git a/greetd/src/error.rs b/greetd/src/error.rs index 0baf640..497a5aa 100644 --- a/greetd/src/error.rs +++ b/greetd/src/error.rs @@ -16,6 +16,9 @@ pub enum Error { #[error("i/o error: {0}")] Io(String), + + #[error("configuration error: {0}")] + ConfigError(String), } impl From> for Error { diff --git a/greetd/src/main.rs b/greetd/src/main.rs index c9e7010..8520b11 100644 --- a/greetd/src/main.rs +++ b/greetd/src/main.rs @@ -21,7 +21,7 @@ use tokio::task; use crate::{error::Error, session::worker}; async fn session_worker_main(config: config::Config) -> Result<(), Error> { - let raw_fd = config.session_worker as RawFd; + let raw_fd = config.internal.session_worker as RawFd; let mut cur_flags = unsafe { FdFlag::from_bits_unchecked(fcntl(raw_fd, FcntlArg::F_GETFD)?) }; cur_flags.insert(FdFlag::FD_CLOEXEC); fcntl(raw_fd, FcntlArg::F_SETFD(cur_flags))?; @@ -31,11 +31,17 @@ async fn session_worker_main(config: config::Config) -> Result<(), Error> { #[tokio::main] async fn main() { - let config = config::read_config(); + let config = match config::read_config() { + Ok(config) => config, + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + }; mlockall(MlockAllFlags::all()).expect("unable to lock pages"); let res = task::LocalSet::new() .run_until(async move { - if config.session_worker > 0 { + if config.internal.session_worker > 0 { session_worker_main(config).await } else { server::main(config).await diff --git a/greetd/src/server.rs b/greetd/src/server.rs index 8b1a361..8857d21 100644 --- a/greetd/src/server.rs +++ b/greetd/src/server.rs @@ -77,17 +77,12 @@ async fn client_handler(ctx: &Context, mut s: UnixStream) -> Result<(), Error> { } pub async fn main(config: Config) -> Result<(), Error> { - std::env::set_var("GREETD_SOCK", &config.socket_path); + std::env::set_var("GREETD_SOCK", &config.internal.socket_path); - let _ = std::fs::remove_file(config.socket_path.clone()); - let mut listener = UnixListener::bind(&config.socket_path) + let _ = std::fs::remove_file(&config.internal.socket_path); + let mut listener = UnixListener::bind(&config.internal.socket_path) .map_err(|e| format!("unable to open listener: {}", e))?; - let u = users::get_user_by_name(&config.greeter_user).ok_or(format!( - "configured greeter user '{}' not found", - &config.greeter_user - ))?; - let service = if Path::new("/etc/pam.d/greetd").exists() { "greetd" } else if Path::new("/etc/pam.d/login").exists() { @@ -97,13 +92,22 @@ pub async fn main(config: Config) -> Result<(), Error> { return Err("PAM 'greetd' service missing".into()); }; + let u = users::get_user_by_name(&config.file.default_session.user).ok_or(format!( + "configured default session user '{}' not found", + &config.file.default_session.user + ))?; + let uid = Uid::from_raw(u.uid()); let gid = Gid::from_raw(u.primary_group_id()); - chown(config.socket_path.as_str(), Some(uid), Some(gid)) - .map_err(|e| format!("unable to chown greetd socket: {}", e))?; + chown(config.internal.socket_path.as_str(), Some(uid), Some(gid)).map_err(|e| { + format!( + "unable to chown greetd socket at {}: {}", + &config.internal.socket_path, e + ) + })?; let term = Terminal::open(0).map_err(|e| format!("unable to open terminal: {}", e))?; - let vt = match config.vt() { + let vt = match config.file.terminal.vt { VtSelection::Current => term .vt_get_current() .map_err(|e| format!("unable to get current VT: {}", e))?, @@ -116,11 +120,12 @@ pub async fn main(config: Config) -> Result<(), Error> { drop(term); let ctx = Rc::new(Context::new( - config.greeter, - config.greeter_user, + config.file.default_session.command, + config.file.default_session.user, vt, service.to_string(), )); + if let Err(e) = ctx.greet().await { eprintln!("unable to start greeter: {}", e); reset_vt(vt).map_err(|e| format!("unable to reset VT: {}", e))?; diff --git a/man/greetd-5.scd b/man/greetd-5.scd index e605813..6bfce31 100644 --- a/man/greetd-5.scd +++ b/man/greetd-5.scd @@ -10,6 +10,10 @@ greetd uses a simple TOML configuration file to define its behavior. # CONFIGURATION +## terminal + +This section contains terminal configuration. + *vt* = num|"next"|"current" The VT to run on. Can be the number of a specific VT, "next" to select the next available VT, or "current" to stay wherever greetd was started. The @@ -23,16 +27,44 @@ greetd uses a simple TOML configuration file to define its behavior. Use of a specific VT with appropriate conflict avoidance is recommended. -*greeter* = command-line - The command-line to run to start the greeter, e.g. "agreety -c sway". The - greeter will be run when greetd is initially launched, and every time a - created session terminates. +## default_session + +This section describes the default session, also referred to as the "greeter". + +*command* = command-line + The command-line to run to start the default session, e.g. "agreety -c sway". + The default session is automatically started when no other session is + running, such as when user session terminate, and when greetd is initially + started with no initial session configured. See *greetd-ipc*(7) for information on how a greeter can create sessions. -*greeter_user* = user +*user* = user The user to use for running the greeter. Defaults to "greeter". +## initial_session + +This optional section describes the initial session, commonly referred to as +"auto-login". + +*command* = command-line + The command-line to run to start the initial session, e.g. "sway". The + initial session will be run when exactly once when greetd is initially + launched. + +*user* = user + The user to use for running the initial session. + +# EXAMPLE + +```toml +[terminal] +vt = 1 + +[default_session] +command = "agreety -c sway" +``` + # AUTHORS Maintained by Kenny Levinsen . For more information about